diff options
64 files changed, 3189 insertions, 4659 deletions
diff --git a/src/common/Banner.cpp b/src/common/Banner.cpp index b2a9517658..6e6db0fcc7 100644 --- a/src/common/Banner.cpp +++ b/src/common/Banner.cpp @@ -28,4 +28,6 @@ void Acore::Banner::Show(char const* applicationName, void(*log)(char const* tex { logExtraInfo(); } + + log(" "); } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7a2997089b..704cdc8bba 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -52,7 +52,6 @@ target_link_libraries(common PRIVATE acore-core-interface PUBLIC - ace boost argon2 g3dlib diff --git a/src/common/Common.h b/src/common/Common.h index cb488bd002..76d4073afc 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -14,13 +14,12 @@ #include <utility> #if AC_PLATFORM == AC_PLATFORM_WINDOWS -#include <ace/config-all.h> #include <ws2tcpip.h> #if AC_COMPILER == AC_COMPILER_INTEL # if !defined(BOOST_ASIO_HAS_MOVE) # define BOOST_ASIO_HAS_MOVE # endif // !defined(BOOST_ASIO_HAS_MOVE) -# endif // if WARHEAD_COMPILER == WARHEAD_COMPILER_INTEL +# endif // if AC_COMPILER == AC_COMPILER_INTEL #else #include <cstdlib> #include <netdb.h> diff --git a/src/common/Define.h b/src/common/Define.h index 73857029c1..1720eb4185 100644 --- a/src/common/Define.h +++ b/src/common/Define.h @@ -16,12 +16,12 @@ #define ACORE_BIGENDIAN 1 #if !defined(ACORE_ENDIAN) -# if defined (ACE_BIG_ENDIAN) +# if defined (BOOST_BIG_ENDIAN) # define ACORE_ENDIAN ACORE_BIGENDIAN -# else //ACE_BYTE_ORDER != ACE_BIG_ENDIAN +# else # define ACORE_ENDIAN ACORE_LITTLEENDIAN -# endif //ACE_BYTE_ORDER -#endif //ACORE_ENDIAN +# endif +#endif #if AC_PLATFORM == AC_PLATFORM_WINDOWS # define ACORE_PATH_MAX MAX_PATH diff --git a/src/common/Platform/ServiceWin32.cpp b/src/common/Platform/ServiceWin32.cpp index 7a96fccae1..475529a8f5 100644 --- a/src/common/Platform/ServiceWin32.cpp +++ b/src/common/Platform/ServiceWin32.cpp @@ -12,11 +12,6 @@ #include <windows.h> #include <winsvc.h> -// stupid ACE define -#ifdef main -#undef main -#endif //main - #if !defined(WINADVAPI) #if !defined(_ADVAPI32_) #define WINADVAPI DECLSPEC_IMPORT diff --git a/src/common/Threading/MPSCQueue.h b/src/common/Threading/MPSCQueue.h new file mode 100644 index 0000000000..23b92b4a39 --- /dev/null +++ b/src/common/Threading/MPSCQueue.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + */ + +#ifndef MPSCQueue_h__ +#define MPSCQueue_h__ + +#include <atomic> +#include <utility> + +namespace Acore::Impl +{ + // C++ implementation of Dmitry Vyukov's lock free MPSC queue + // http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue + template<typename T> + class MPSCQueueNonIntrusive + { + public: + MPSCQueueNonIntrusive() : _head(new Node()), _tail(_head.load(std::memory_order_relaxed)) + { + Node* front = _head.load(std::memory_order_relaxed); + front->Next.store(nullptr, std::memory_order_relaxed); + } + + ~MPSCQueueNonIntrusive() + { + T* output; + while (Dequeue(output)) + delete output; + + Node* front = _head.load(std::memory_order_relaxed); + delete front; + } + + void Enqueue(T* input) + { + Node* node = new Node(input); + Node* prevHead = _head.exchange(node, std::memory_order_acq_rel); + prevHead->Next.store(node, std::memory_order_release); + } + + bool Dequeue(T*& result) + { + Node* tail = _tail.load(std::memory_order_relaxed); + Node* next = tail->Next.load(std::memory_order_acquire); + if (!next) + return false; + + result = next->Data; + _tail.store(next, std::memory_order_release); + delete tail; + return true; + } + + private: + struct Node + { + Node() = default; + explicit Node(T* data) : Data(data) + { + Next.store(nullptr, std::memory_order_relaxed); + } + + T* Data; + std::atomic<Node*> Next; + }; + + std::atomic<Node*> _head; + std::atomic<Node*> _tail; + + MPSCQueueNonIntrusive(MPSCQueueNonIntrusive const&) = delete; + MPSCQueueNonIntrusive& operator=(MPSCQueueNonIntrusive const&) = delete; + }; + + // C++ implementation of Dmitry Vyukov's lock free MPSC queue + // http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue + template<typename T, std::atomic<T*> T::* IntrusiveLink> + class MPSCQueueIntrusive + { + public: + MPSCQueueIntrusive() : _dummyPtr(reinterpret_cast<T*>(std::addressof(_dummy))), _head(_dummyPtr), _tail(_dummyPtr) + { + // _dummy is constructed from aligned_storage and is intentionally left uninitialized (it might not be default constructible) + // so we init only its IntrusiveLink here + std::atomic<T*>* dummyNext = new (&(_dummyPtr->*IntrusiveLink)) std::atomic<T*>(); + dummyNext->store(nullptr, std::memory_order_relaxed); + } + + ~MPSCQueueIntrusive() + { + T* output; + while (Dequeue(output)) + delete output; + } + + void Enqueue(T* input) + { + (input->*IntrusiveLink).store(nullptr, std::memory_order_release); + T* prevHead = _head.exchange(input, std::memory_order_acq_rel); + (prevHead->*IntrusiveLink).store(input, std::memory_order_release); + } + + bool Dequeue(T*& result) + { + T* tail = _tail.load(std::memory_order_relaxed); + T* next = (tail->*IntrusiveLink).load(std::memory_order_acquire); + if (tail == _dummyPtr) + { + if (!next) + return false; + + _tail.store(next, std::memory_order_release); + tail = next; + next = (next->*IntrusiveLink).load(std::memory_order_acquire); + } + + if (next) + { + _tail.store(next, std::memory_order_release); + result = tail; + return true; + } + + T* head = _head.load(std::memory_order_acquire); + if (tail != head) + return false; + + Enqueue(_dummyPtr); + next = (tail->*IntrusiveLink).load(std::memory_order_acquire); + if (next) + { + _tail.store(next, std::memory_order_release); + result = tail; + return true; + } + return false; + } + + private: + std::aligned_storage_t<sizeof(T), alignof(T)> _dummy; + T* _dummyPtr; + std::atomic<T*> _head; + std::atomic<T*> _tail; + + MPSCQueueIntrusive(MPSCQueueIntrusive const&) = delete; + MPSCQueueIntrusive& operator=(MPSCQueueIntrusive const&) = delete; + }; +} + +template<typename T, std::atomic<T*> T::* IntrusiveLink = nullptr> +using MPSCQueue = std::conditional_t<IntrusiveLink != nullptr, Acore::Impl::MPSCQueueIntrusive<T, IntrusiveLink>, Acore::Impl::MPSCQueueNonIntrusive<T>>; + +#endif // MPSCQueue_h__ diff --git a/src/common/Threading/PCQueue.h b/src/common/Threading/PCQueue.h index 97120888e8..29320b51be 100644 --- a/src/common/Threading/PCQueue.h +++ b/src/common/Threading/PCQueue.h @@ -100,7 +100,7 @@ private: typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; } template<typename E = T> - typename std::enable_if < !std::is_pointer<E>::value >::type DeleteQueuedObject(E const& /*packet*/) { } + typename std::enable_if<!std::is_pointer<E>::value>::type DeleteQueuedObject(E const& /*packet*/) { } }; #endif diff --git a/src/common/Threading/ProducerConsumerQueue.h b/src/common/Threading/ProducerConsumerQueue.h deleted file mode 100644 index 4ad29f174a..0000000000 --- a/src/common/Threading/ProducerConsumerQueue.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 - * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> - */ - -#ifndef _PCQ_H -#define _PCQ_H - -#include <condition_variable> -#include <mutex> -#include <queue> -#include <atomic> -#include <type_traits> - -template <typename T> -class ProducerConsumerQueue -{ -private: - std::mutex _queueLock; - std::queue<T> _queue; - std::condition_variable _condition; - std::atomic<bool> _shutdown; - -public: - - ProducerConsumerQueue<T>() : _shutdown(false) { } - - void Push(const T& value) - { - std::lock_guard<std::mutex> lock(_queueLock); - _queue.push(std::move(value)); - - _condition.notify_one(); - } - - bool Empty() - { - std::lock_guard<std::mutex> lock(_queueLock); - - return _queue.empty(); - } - - bool Pop(T& value) - { - std::lock_guard<std::mutex> lock(_queueLock); - - if (_queue.empty() || _shutdown) - return false; - - value = _queue.front(); - - _queue.pop(); - - return true; - } - - void WaitAndPop(T& value) - { - std::unique_lock<std::mutex> lock(_queueLock); - - // we could be using .wait(lock, predicate) overload here but it is broken - // https://connect.microsoft.com/VisualStudio/feedback/details/1098841 - while (_queue.empty() && !_shutdown) - _condition.wait(lock); - - if (_queue.empty() || _shutdown) - return; - - value = _queue.front(); - - _queue.pop(); - } - - void Cancel() - { - std::unique_lock<std::mutex> lock(_queueLock); - - while (!_queue.empty()) - { - T& value = _queue.front(); - - DeleteQueuedObject(value); - - _queue.pop(); - } - - _shutdown = true; - - _condition.notify_all(); - } - -private: - template<typename E = T> - typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; } - - template<typename E = T> - typename std::enable_if<!std::is_pointer<E>::value>::type DeleteQueuedObject(E const& /*packet*/) { } -}; - -#endif diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp index 3df1616898..5628323251 100644 --- a/src/common/Utilities/Util.cpp +++ b/src/common/Utilities/Util.cpp @@ -7,9 +7,9 @@ #include "Util.h" #include "Common.h" #include "Containers.h" +#include "IpAddress.h" #include "StringConvert.h" #include "StringFormat.h" -#include <ace/Default_Constants.h> #include <algorithm> #include <cctype> #include <cstdarg> @@ -17,8 +17,10 @@ #include <iomanip> #include <sstream> #include <string> +#include <cctype> +#include <cstdarg> +#include <ctime> #include <utf8.h> -// #include "IpAddress.h" Tokenizer::Tokenizer(const std::string& src, const char sep, uint32 vectorReserve) { @@ -295,26 +297,9 @@ bool IsIPAddress(char const* ipaddress) return false; } - // Let the big boys do it. - // Drawback: all valid ip address formats are recognized e.g.: 12.23, 121234, 0xABCD) - return inet_addr(ipaddress) != INADDR_NONE; -} - -std::string GetAddressString(ACE_INET_Addr const& addr) -{ - char buf[ACE_MAX_FULLY_QUALIFIED_NAME_LEN + 16]; - addr.addr_to_string(buf, ACE_MAX_FULLY_QUALIFIED_NAME_LEN + 16); - return buf; -} - -bool IsIPAddrInNetwork(ACE_INET_Addr const& net, ACE_INET_Addr const& addr, ACE_INET_Addr const& subnetMask) -{ - uint32 mask = subnetMask.get_ip_address(); - if ((net.get_ip_address() & mask) == (addr.get_ip_address() & mask)) - { - return true; - } - return false; + boost::system::error_code error; + Acore::Net::make_address(ipaddress, error); + return !error; } /// create PID file diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index 09b9cc11fa..6bb20d6fcc 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -10,7 +10,6 @@ #include "Containers.h" #include "Define.h" #include "Errors.h" -#include <ace/INET_Addr.h> #include <algorithm> #include <array> #include <cctype> @@ -18,6 +17,9 @@ #include <map> #include <string> #include <vector> +#include <list> +#include <map> +#include <array> // Searcher for map of structs template<typename T, class S> struct Finder @@ -383,12 +385,6 @@ bool Utf8ToUpperOnlyLatin(std::string& utf8String); bool IsIPAddress(char const* ipaddress); -/// Checks if address belongs to the a network with specified submask -bool IsIPAddrInNetwork(ACE_INET_Addr const& net, ACE_INET_Addr const& addr, ACE_INET_Addr const& subnetMask); - -/// Transforms ACE_INET_Addr address into string format "dotted_ip:port" -std::string GetAddressString(ACE_INET_Addr const& addr); - uint32 CreatePIDFile(const std::string& filename); uint32 GetPID(); diff --git a/src/server/authserver/Main.cpp b/src/server/authserver/Main.cpp index 0447337c5f..a9505596d2 100644 --- a/src/server/authserver/Main.cpp +++ b/src/server/authserver/Main.cpp @@ -13,28 +13,26 @@ */ #include "AppenderDB.h" +#include "AuthSocketMgr.h" #include "Banner.h" #include "Common.h" #include "Config.h" #include "DatabaseEnv.h" #include "DatabaseLoader.h" +#include "DeadlineTimer.h" #include "GitRevision.h" #include "IPLocation.h" #include "Log.h" -#include "RealmAcceptor.h" -#include "RealmList.h" -#include "DatabaseLoader.h" +#include "IoContext.h" #include "MySQLThreading.h" +#include "ProcessPriority.h" +#include "RealmList.h" #include "SecretMgr.h" #include "SharedDefines.h" -#include "SignalHandler.h" #include "Util.h" -#include "ProcessPriority.h" -#include <ace/ACE.h> -#include <ace/Dev_Poll_Reactor.h> -#include <ace/Sig_Handler.h> -#include <ace/TP_Reactor.h> +#include <boost/asio/signal_set.hpp> #include <boost/version.hpp> +#include <csignal> #include <openssl/crypto.h> #include <openssl/opensslv.h> @@ -42,23 +40,26 @@ #define _ACORE_REALM_CONFIG "authserver.conf" #endif +using boost::asio::ip::tcp; + bool StartDB(); void StopDB(); - -bool stopEvent = false; // Setting it to true stops the server +void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int signalNumber); +void KeepDatabaseAliveHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error); +void BanExpiryHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error); /// Print out the usage string for this program on the console. void usage(const char* prog) { LOG_INFO("server.authserver", "Usage: \n %s [<options>]\n" - " -c config_file use config_file as configuration file\n\r", - prog); + " -c config_file use config_file as configuration file\n\r", prog); } /// Launch the auth server -extern int main(int argc, char** argv) +int main(int argc, char** argv) { Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_AUTHSERVER; + signal(SIGABRT, &Acore::AbortHandler); // Command line parsing to get the configuration file name std::string configFile = sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG); @@ -99,18 +100,9 @@ extern int main(int argc, char** argv) LOG_INFO("server.authserver", "> Using configuration file %s.", sConfigMgr->GetFilename().c_str()); LOG_INFO("server.authserver", "> Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); LOG_INFO("server.authserver", "> Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); - LOG_INFO("server.authserver", "> Using ACE version: %s\n", ACE_VERSION); } ); -#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL) - ACE_Reactor::instance(new ACE_Reactor(new ACE_Dev_Poll_Reactor(ACE::max_handles(), 1), 1), true); -#else - ACE_Reactor::instance(new ACE_Reactor(new ACE_TP_Reactor(), true), true); -#endif - - LOG_INFO("server.authserver", "Max allowed open files is %d", ACE::max_handles()); - // authserver PID file creation std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", ""); if (!pidFile.empty()) @@ -133,75 +125,70 @@ extern int main(int argc, char** argv) // Load IP Location Database sIPLocation->Load(); + std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); }); + + std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>(); + // Get the list of realms for the server - sRealmList->Initialize(sConfigMgr->GetOption<int32>("RealmsStateUpdateDelay", 20)); + sRealmList->Initialize(*ioContext, sConfigMgr->GetOption<int32>("RealmsStateUpdateDelay", 20)); + + std::shared_ptr<void> sRealmListHandle(nullptr, [](void*) { sRealmList->Close(); }); + if (sRealmList->GetRealms().empty()) { LOG_ERROR("server.authserver", "No valid realms specified."); return 1; } - // Launch the listening network socket - RealmAcceptor acceptor; - - int32 rmport = sConfigMgr->GetOption<int32>("RealmServerPort", 3724); - if (rmport < 0 || rmport > 0xFFFF) + // Start the listening port (acceptor) for auth connections + int32 port = sConfigMgr->GetOption<int32>("RealmServerPort", 3724); + if (port < 0 || port > 0xFFFF) { - LOG_ERROR("server.authserver", "The specified RealmServerPort (%d) is out of the allowed range (1-65535)", rmport); + LOG_ERROR("server.authserver", "Specified port out of allowed range (1-65535)"); return 1; } - std::string bind_ip = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0"); + std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0"); - ACE_INET_Addr bind_addr(uint16(rmport), bind_ip.c_str()); - - if (acceptor.open(bind_addr, ACE_Reactor::instance(), ACE_NONBLOCK) == -1) + if (!sAuthSocketMgr.StartNetwork(*ioContext, bindIp, port)) { - LOG_ERROR("server.authserver", "Auth server can not bind to %s:%d (possible error: port already in use)", bind_ip.c_str(), rmport); + LOG_ERROR("server.authserver", "Failed to initialize network"); return 1; } - LOG_INFO("server.authserver", "Authserver listening to %s:%d", bind_ip.c_str(), rmport); - - // Initialize the signal handlers - Acore::SignalHandler signalHandler; - auto const _handler = [](int) { stopEvent = true; }; + std::shared_ptr<void> sAuthSocketMgrHandle(nullptr, [](void*) { sAuthSocketMgr.StopNetwork(); }); - // Register authservers's signal handlers - signalHandler.handle_signal(SIGINT, _handler); - signalHandler.handle_signal(SIGTERM, _handler); + // Set signal handlers + boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM); #if AC_PLATFORM == AC_PLATFORM_WINDOWS - signalHandler.handle_signal(SIGBREAK, _handler); + signals.add(SIGBREAK); #endif + signals.async_wait(std::bind(&SignalHandler, std::weak_ptr<Acore::Asio::IoContext>(ioContext), std::placeholders::_1, std::placeholders::_2)); // Set process priority according to configuration settings SetProcessPriority("server.authserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false)); - // maximum counter for next ping - uint32 numLoops = (sConfigMgr->GetOption<int32>("MaxPingTime", 30) * (MINUTE * 1000000 / 100000)); - uint32 loopCounter = 0; + // Enabled a timed callback for handling the database keep alive ping + int32 dbPingInterval = sConfigMgr->GetOption<int32>("MaxPingTime", 30); + std::shared_ptr<Acore::Asio::DeadlineTimer> dbPingTimer = std::make_shared<Acore::Asio::DeadlineTimer>(*ioContext); + dbPingTimer->expires_from_now(boost::posix_time::minutes(dbPingInterval)); + dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, std::weak_ptr<Acore::Asio::DeadlineTimer>(dbPingTimer), dbPingInterval, std::placeholders::_1)); - // Wait for termination signal - while (!stopEvent) - { - // dont move this outside the loop, the reactor will modify it - ACE_Time_Value interval(0, 100000); + int32 banExpiryCheckInterval = sConfigMgr->GetOption<int32>("BanExpiryCheckInterval", 60); + std::shared_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimer = std::make_shared<Acore::Asio::DeadlineTimer>(*ioContext); + banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(banExpiryCheckInterval)); + banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, std::weak_ptr<Acore::Asio::DeadlineTimer>(banExpiryCheckTimer), banExpiryCheckInterval, std::placeholders::_1)); - if (ACE_Reactor::instance()->run_reactor_event_loop(interval) == -1) - break; + // Start the io service worker loop + ioContext->run(); - if ((++loopCounter) == numLoops) - { - loopCounter = 0; - LOG_INFO("server.authserver", "Ping MySQL to keep connection alive"); - LoginDatabase.KeepAlive(); - } - } - - // Close the Database Pool and library - StopDB(); + banExpiryCheckTimer->cancel(); + dbPingTimer->cancel(); LOG_INFO("server.authserver", "Halting process..."); + + signals.cancel(); + return 0; } @@ -231,3 +218,44 @@ void StopDB() LoginDatabase.Close(); MySQL::Library_End(); } + +void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int /*signalNumber*/) +{ + if (!error) + { + if (std::shared_ptr<Acore::Asio::IoContext> ioContext = ioContextRef.lock()) + { + ioContext->stop(); + } + } +} + +void KeepDatabaseAliveHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error) +{ + if (!error) + { + if (std::shared_ptr<Acore::Asio::DeadlineTimer> dbPingTimer = dbPingTimerRef.lock()) + { + LOG_INFO("server.authserver", "Ping MySQL to keep connection alive"); + LoginDatabase.KeepAlive(); + + dbPingTimer->expires_from_now(boost::posix_time::minutes(dbPingInterval)); + dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, dbPingTimerRef, dbPingInterval, std::placeholders::_1)); + } + } +} + +void BanExpiryHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error) +{ + if (!error) + { + if (std::shared_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimer = banExpiryCheckTimerRef.lock()) + { + LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); + LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); + + banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(banExpiryCheckInterval)); + banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, banExpiryCheckTimerRef, banExpiryCheckInterval, std::placeholders::_1)); + } + } +} diff --git a/src/server/authserver/PrecompiledHeaders/authPCH.h b/src/server/authserver/PrecompiledHeaders/authPCH.h index 5fc3b0a341..3c813ae54d 100644 --- a/src/server/authserver/PrecompiledHeaders/authPCH.h +++ b/src/server/authserver/PrecompiledHeaders/authPCH.h @@ -1,6 +1,5 @@ -#include "Configuration/Config.h" -#include "Database/DatabaseEnv.h" +#include "Config.h" +#include "DatabaseEnv.h" #include "Log.h" #include "RealmList.h" -#include "RealmSocket.h" #include "Common.h" diff --git a/src/server/authserver/Server/AuthSession.cpp b/src/server/authserver/Server/AuthSession.cpp new file mode 100644 index 0000000000..b5c3f66c33 --- /dev/null +++ b/src/server/authserver/Server/AuthSession.cpp @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + */ + +#include "AuthSession.h" +#include "AES.h" +#include "AuthCodes.h" +#include "Config.h" +#include "CryptoGenerics.h" +#include "CryptoRandom.h" +#include "DatabaseEnv.h" +#include "Errors.h" +#include "CryptoHash.h" +#include "IPLocation.h" +#include "Log.h" +#include "RealmList.h" +#include "SecretMgr.h" +#include "Timer.h" +#include "TOTP.h" +#include "Util.h" +#include <boost/lexical_cast.hpp> +#include <openssl/crypto.h> + +using boost::asio::ip::tcp; + +enum eAuthCmd +{ + AUTH_LOGON_CHALLENGE = 0x00, + AUTH_LOGON_PROOF = 0x01, + AUTH_RECONNECT_CHALLENGE = 0x02, + AUTH_RECONNECT_PROOF = 0x03, + REALM_LIST = 0x10, + XFER_INITIATE = 0x30, + XFER_DATA = 0x31, + XFER_ACCEPT = 0x32, + XFER_RESUME = 0x33, + XFER_CANCEL = 0x34 +}; + +#pragma pack(push, 1) + +typedef struct AUTH_LOGON_CHALLENGE_C +{ + uint8 cmd; + uint8 error; + uint16 size; + uint8 gamename[4]; + uint8 version1; + uint8 version2; + uint8 version3; + uint16 build; + uint8 platform[4]; + uint8 os[4]; + uint8 country[4]; + uint32 timezone_bias; + uint32 ip; + uint8 I_len; + uint8 I[1]; +} sAuthLogonChallenge_C; +static_assert(sizeof(sAuthLogonChallenge_C) == (1 + 1 + 2 + 4 + 1 + 1 + 1 + 2 + 4 + 4 + 4 + 4 + 4 + 1 + 1)); + +typedef struct AUTH_LOGON_PROOF_C +{ + uint8 cmd; + Acore::Crypto::SRP6::EphemeralKey A; + Acore::Crypto::SHA1::Digest clientM; + Acore::Crypto::SHA1::Digest crc_hash; + uint8 number_of_keys; + uint8 securityFlags; +} sAuthLogonProof_C; +static_assert(sizeof(sAuthLogonProof_C) == (1 + 32 + 20 + 20 + 1 + 1)); + +typedef struct AUTH_LOGON_PROOF_S +{ + uint8 cmd; + uint8 error; + Acore::Crypto::SHA1::Digest M2; + uint32 AccountFlags; + uint32 SurveyId; + uint16 LoginFlags; +} sAuthLogonProof_S; +static_assert(sizeof(sAuthLogonProof_S) == (1 + 1 + 20 + 4 + 4 + 2)); + +typedef struct AUTH_LOGON_PROOF_S_OLD +{ + uint8 cmd; + uint8 error; + Acore::Crypto::SHA1::Digest M2; + uint32 unk2; +} sAuthLogonProof_S_Old; +static_assert(sizeof(sAuthLogonProof_S_Old) == (1 + 1 + 20 + 4)); + +typedef struct AUTH_RECONNECT_PROOF_C +{ + uint8 cmd; + uint8 R1[16]; + Acore::Crypto::SHA1::Digest R2, R3; + uint8 number_of_keys; +} sAuthReconnectProof_C; +static_assert(sizeof(sAuthReconnectProof_C) == (1 + 16 + 20 + 20 + 1)); + +#pragma pack(pop) + +std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } }; + +#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16) + +#define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4 +#define REALM_LIST_PACKET_SIZE 5 + +std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers() +{ + std::unordered_map<uint8, AuthHandler> handlers; + + handlers[AUTH_LOGON_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleLogonChallenge }; + handlers[AUTH_LOGON_PROOF] = { STATUS_LOGON_PROOF, sizeof(AUTH_LOGON_PROOF_C), &AuthSession::HandleLogonProof }; + handlers[AUTH_RECONNECT_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleReconnectChallenge }; + handlers[AUTH_RECONNECT_PROOF] = { STATUS_RECONNECT_PROOF, sizeof(AUTH_RECONNECT_PROOF_C), &AuthSession::HandleReconnectProof }; + handlers[REALM_LIST] = { STATUS_AUTHED, REALM_LIST_PACKET_SIZE, &AuthSession::HandleRealmList }; + + return handlers; +} + +std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers(); + +void AccountInfo::LoadResult(Field* fields) +{ + // 0 1 2 3 4 5 + // SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, + // 6 7 + // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, + // 8 9 + // ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate, + // 10 + // aa.gmlevel (, more query-specific fields) + // FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 LEFT JOIN ip_banned ipb ON ipb.ip = ? WHERE a.username = ? + + Id = fields[0].GetUInt32(); + Login = fields[1].GetString(); + IsLockedToIP = fields[2].GetBool(); + LockCountry = fields[3].GetString(); + LastIP = fields[4].GetString(); + FailedLogins = fields[5].GetUInt32(); + IsBanned = fields[6].GetBool() || fields[8].GetBool(); + IsPermanentlyBanned = fields[7].GetBool() || fields[9].GetBool(); + SecurityLevel = static_cast<AccountTypes>(fields[10].GetUInt8()) > SEC_CONSOLE ? SEC_CONSOLE : static_cast<AccountTypes>(fields[10].GetUInt8()); + + // Use our own uppercasing of the account name instead of using UPPER() in mysql query + // This is how the account was created in the first place and changing it now would result in breaking + // login for all accounts having accented characters in their name + Utf8ToUpperOnlyLatin(Login); +} + +AuthSession::AuthSession(tcp::socket&& socket) : + Socket(std::move(socket)), _status(STATUS_CHALLENGE), _build(0), _expversion(0) { } + +void AuthSession::Start() +{ + std::string ip_address = GetRemoteIpAddress().to_string(); + LOG_TRACE("session", "Accepted connection from %s", ip_address.c_str()); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO); + stmt->setString(0, ip_address); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1))); +} + +bool AuthSession::Update() +{ + if (!AuthSocket::Update()) + return false; + + _queryProcessor.ProcessReadyCallbacks(); + + return true; +} + +void AuthSession::CheckIpCallback(PreparedQueryResult result) +{ + if (result) + { + bool banned = false; + + do + { + Field* fields = result->Fetch(); + if (fields[0].GetUInt64() != 0) + banned = true; + + } while (result->NextRow()); + + if (banned) + { + ByteBuffer pkt; + pkt << uint8(AUTH_LOGON_CHALLENGE); + pkt << uint8(0x00); + pkt << uint8(WOW_FAIL_BANNED); + SendPacket(pkt); + LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '%s:%d' tries to login!", GetRemoteIpAddress().to_string().c_str(), GetRemotePort()); + return; + } + } + + AsyncRead(); +} + +void AuthSession::ReadHandler() +{ + MessageBuffer& packet = GetReadBuffer(); + + while (packet.GetActiveSize()) + { + uint8 cmd = packet.GetReadPointer()[0]; + auto itr = Handlers.find(cmd); + if (itr == Handlers.end()) + { + // well we dont handle this, lets just ignore it + packet.Reset(); + break; + } + + if (_status != itr->second.status) + { + CloseSocket(); + return; + } + + uint16 size = uint16(itr->second.packetSize); + if (packet.GetActiveSize() < size) + break; + + if (cmd == AUTH_LOGON_CHALLENGE || cmd == AUTH_RECONNECT_CHALLENGE) + { + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(packet.GetReadPointer()); + size += challenge->size; + if (size > MAX_ACCEPTED_CHALLENGE_SIZE) + { + CloseSocket(); + return; + } + } + + if (packet.GetActiveSize() < size) + break; + + if (!(*this.*itr->second.handler)()) + { + CloseSocket(); + return; + } + + packet.ReadCompleted(size); + } + + AsyncRead(); +} + +void AuthSession::SendPacket(ByteBuffer& packet) +{ + if (!IsOpen()) + return; + + if (!packet.empty()) + { + MessageBuffer buffer(packet.size()); + buffer.Write(packet.contents(), packet.size()); + QueuePacket(std::move(buffer)); + } +} + +bool AuthSession::HandleLogonChallenge() +{ + _status = STATUS_CLOSED; + + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len) + return false; + + std::string login((char const*)challenge->I, challenge->I_len); + LOG_DEBUG("server.authserver", "[AuthChallenge] '%s'", login.c_str()); + + _build = challenge->build; + _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); + std::array<char, 5> os; + os.fill('\0'); + memcpy(os.data(), challenge->os, sizeof(challenge->os)); + _os = os.data(); + + // Restore string order as its byte order is reversed + std::reverse(_os.begin(), _os.end()); + + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = challenge->country[4 - i - 1]; + + // Get the account details from the account table + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); + stmt->setString(0, GetRemoteIpAddress().to_string()); + stmt->setString(1, login); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1))); + return true; +} + +void AuthSession::LogonChallengeCallback(PreparedQueryResult result) +{ + ByteBuffer pkt; + pkt << uint8(AUTH_LOGON_CHALLENGE); + pkt << uint8(0x00); + + if (!result) + { + pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + SendPacket(pkt); + return; + } + + Field* fields = result->Fetch(); + + _accountInfo.LoadResult(fields); + + std::string ipAddress = GetRemoteIpAddress().to_string(); + uint16 port = GetRemotePort(); + + // If the IP is 'locked', check that the player comes indeed from the correct IP address + if (_accountInfo.IsLockedToIP) + { + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s' is logging in from '%s'", _accountInfo.Login.c_str(), _accountInfo.LastIP.c_str(), ipAddress.c_str()); + if (_accountInfo.LastIP != ipAddress) + { + pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); + SendPacket(pkt); + return; + } + } + else + { + if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ipAddress)) + _ipCountry = location->CountryCode; + + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _accountInfo.Login.c_str()); + if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00") + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _accountInfo.Login.c_str()); + else if (!_ipCountry.empty()) + { + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _accountInfo.Login.c_str(), _accountInfo.LockCountry.c_str(), _ipCountry.c_str()); + if (_ipCountry != _accountInfo.LockCountry) + { + pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); + SendPacket(pkt); + return; + } + } + } + + // If the account is banned, reject the logon attempt + if (_accountInfo.IsBanned) + { + if (_accountInfo.IsPermanentlyBanned) + { + pkt << uint8(WOW_FAIL_BANNED); + SendPacket(pkt); + LOG_INFO("server.authserver.banned", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); + return; + } + else + { + pkt << uint8(WOW_FAIL_SUSPENDED); + SendPacket(pkt); + LOG_INFO("server.authserver.banned", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); + return; + } + } + + uint8 securityFlags = 0; + + // Check if a TOTP token is needed + if (!fields[11].IsNull()) + { + securityFlags = 4; + _totpSecret = fields[11].GetBinary(); + + if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY)) + { + bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(*_totpSecret, *secret); + if (!success) + { + pkt << uint8(WOW_FAIL_DB_BUSY); + LOG_ERROR("server.authserver", "[AuthChallenge] Account '%s' has invalid ciphertext for TOTP token key stored", _accountInfo.Login.c_str()); + SendPacket(pkt); + return; + } + } + } + + _srp6.emplace( + _accountInfo.Login, + fields[12].GetBinary<Acore::Crypto::SRP6::SALT_LENGTH>(), + fields[13].GetBinary<Acore::Crypto::SRP6::VERIFIER_LENGTH>()); + + // Fill the response packet with the result + if (AuthHelper::IsAcceptedClientBuild(_build)) + { + pkt << uint8(WOW_SUCCESS); + + pkt.append(_srp6->B); + pkt << uint8(1); + pkt.append(_srp6->g); + pkt << uint8(32); + pkt.append(_srp6->N); + pkt.append(_srp6->s); + pkt.append(VersionChallenge.data(), VersionChallenge.size()); + pkt << uint8(securityFlags); // security flags (0x0...0x04) + + if (securityFlags & 0x01) // PIN input + { + pkt << uint32(0); + pkt << uint64(0) << uint64(0); // 16 bytes hash? + } + + if (securityFlags & 0x02) // Matrix input + { + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint64(0); + } + + if (securityFlags & 0x04) // Security token input + pkt << uint8(1); + + LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%s' locale (%u)", + ipAddress.c_str(), port, _accountInfo.Login.c_str(), _localizationName.c_str(), GetLocaleByName(_localizationName)); + + _status = STATUS_LOGON_PROOF; + } + else + pkt << uint8(WOW_FAIL_VERSION_INVALID); + + SendPacket(pkt); +} + +// Logon Proof command handler +bool AuthSession::HandleLogonProof() +{ + LOG_DEBUG("server.authserver", "Entering _HandleLogonProof"); + _status = STATUS_CLOSED; + + // Read the packet + sAuthLogonProof_C* logonProof = reinterpret_cast<sAuthLogonProof_C*>(GetReadBuffer().GetReadPointer()); + + // If the client has no valid version + if (_expversion == NO_VALID_EXP_FLAG) + { + // Check if we have the appropriate patch on the disk + LOG_DEBUG("network", "Client with invalid version, patching is not implemented"); + return false; + } + + // Check if SRP6 results match (password is correct), else send an error + if (Optional<SessionKey> K = _srp6->VerifyChallengeResponse(logonProof->A, logonProof->clientM)) + { + _sessionKey = *K; + // Check auth token + bool tokenSuccess = false; + bool sentToken = (logonProof->securityFlags & 0x04); + if (sentToken && _totpSecret) + { + uint8 size = *(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C)); + std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size); + GetReadBuffer().ReadCompleted(sizeof(size) + size); + + uint32 incomingToken = atoi(token.c_str()); + tokenSuccess = Acore::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken); + memset(_totpSecret->data(), 0, _totpSecret->size()); + } + else if (!sentToken && !_totpSecret) + tokenSuccess = true; + + if (!tokenSuccess) + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + packet << uint16(0); // LoginFlags, 1 has account message + SendPacket(packet); + return true; + } + + if (!VerifyVersion(logonProof->A.data(), logonProof->A.size(), logonProof->crc_hash, false)) + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_VERSION_INVALID); + SendPacket(packet); + return true; + } + + LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str()); + + // Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account + // No SQL injection (escaped user name) and IP address as received by socket + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF); + stmt->setBinary(0, _sessionKey); + stmt->setString(1, GetRemoteIpAddress().to_string()); + stmt->setUInt32(2, GetLocaleByName(_localizationName)); + stmt->setString(3, _os); + stmt->setString(4, _accountInfo.Login); + LoginDatabase.DirectExecute(stmt); + + // Finish SRP6 and send the final result to the client + Acore::Crypto::SHA1::Digest M2 = Acore::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey); + + ByteBuffer packet; + if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients + { + sAuthLogonProof_S proof; + proof.M2 = M2; + proof.cmd = AUTH_LOGON_PROOF; + proof.error = 0; + proof.AccountFlags = 0x00800000; // 0x01 = GM, 0x08 = Trial, 0x00800000 = Pro pass (arena tournament) + proof.SurveyId = 0; + proof.LoginFlags = 0; // 0x1 = has account message + + packet.resize(sizeof(proof)); + std::memcpy(packet.contents(), &proof, sizeof(proof)); + } + else + { + sAuthLogonProof_S_Old proof; + proof.M2 = M2; + proof.cmd = AUTH_LOGON_PROOF; + proof.error = 0; + proof.unk2 = 0x00; + + packet.resize(sizeof(proof)); + std::memcpy(packet.contents(), &proof, sizeof(proof)); + } + + SendPacket(packet); + _status = STATUS_AUTHED; + } + else + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + packet << uint16(0); // LoginFlags, 1 has account message + SendPacket(packet); + + LOG_INFO("server.authserver.hack", "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!", + GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str()); + + uint32 MaxWrongPassCount = sConfigMgr->GetOption<int32>("WrongPass.MaxCount", 0); + + // We can not include the failed account login hook. However, this is a workaround to still log this. + if (sConfigMgr->GetOption<bool>("WrongPass.Logging", false)) + { + LoginDatabasePreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING); + logstmt->setUInt32(0, _accountInfo.Id); + logstmt->setString(1, GetRemoteIpAddress().to_string()); + logstmt->setString(2, "Login to WoW Failed - Incorrect Password"); + + LoginDatabase.Execute(logstmt); + } + + if (MaxWrongPassCount > 0) + { + //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS); + stmt->setString(0, _accountInfo.Login); + LoginDatabase.Execute(stmt); + + if (++_accountInfo.FailedLogins >= MaxWrongPassCount) + { + uint32 WrongPassBanTime = sConfigMgr->GetOption<int32>("WrongPass.BanTime", 600); + bool WrongPassBanType = sConfigMgr->GetOption<bool>("WrongPass.BanType", false); + + if (WrongPassBanType) + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED); + stmt->setUInt32(0, _accountInfo.Id); + stmt->setUInt32(1, WrongPassBanTime); + LoginDatabase.Execute(stmt); + + LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times", + GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str(), WrongPassBanTime, _accountInfo.FailedLogins); + } + else + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); + stmt->setString(0, GetRemoteIpAddress().to_string()); + stmt->setUInt32(1, WrongPassBanTime); + LoginDatabase.Execute(stmt); + + LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] IP got banned for '%u' seconds because account %s failed to authenticate '%u' times", + GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login.c_str(), _accountInfo.FailedLogins); + } + } + } + } + + return true; +} + +bool AuthSession::HandleReconnectChallenge() +{ + _status = STATUS_CLOSED; + + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len) + return false; + + std::string login((char const*)challenge->I, challenge->I_len); + LOG_DEBUG("server.authserver", "[ReconnectChallenge] '%s'", login.c_str()); + + _build = challenge->build; + _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); + + std::array<char, 5> os; + os.fill('\0'); + memcpy(os.data(), challenge->os, sizeof(challenge->os)); + _os = os.data(); + + // Restore string order as its byte order is reversed + std::reverse(_os.begin(), _os.end()); + + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = challenge->country[4 - i - 1]; + + // Get the account details from the account table + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE); + stmt->setString(0, GetRemoteIpAddress().to_string()); + stmt->setString(1, login); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::ReconnectChallengeCallback, this, std::placeholders::_1))); + return true; +} + +void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result) +{ + ByteBuffer pkt; + pkt << uint8(AUTH_RECONNECT_CHALLENGE); + + if (!result) + { + pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + SendPacket(pkt); + return; + } + + Field* fields = result->Fetch(); + + _accountInfo.LoadResult(fields); + _sessionKey = fields[11].GetBinary<SESSION_KEY_LENGTH>(); + Acore::Crypto::GetRandomBytes(_reconnectProof); + _status = STATUS_RECONNECT_PROOF; + + pkt << uint8(WOW_SUCCESS); + pkt.append(_reconnectProof); + pkt.append(VersionChallenge.data(), VersionChallenge.size()); + + SendPacket(pkt); +} + +bool AuthSession::HandleReconnectProof() +{ + LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof"); + _status = STATUS_CLOSED; + + sAuthReconnectProof_C* reconnectProof = reinterpret_cast<sAuthReconnectProof_C*>(GetReadBuffer().GetReadPointer()); + + if (_accountInfo.Login.empty()) + return false; + + BigNumber t1; + t1.SetBinary(reconnectProof->R1, 16); + + Acore::Crypto::SHA1 sha; + sha.UpdateData(_accountInfo.Login); + sha.UpdateData(t1.ToByteArray<16>()); + sha.UpdateData(_reconnectProof); + sha.UpdateData(_sessionKey); + sha.Finalize(); + + if (sha.GetDigest() == reconnectProof->R2) + { + if (!VerifyVersion(reconnectProof->R1, sizeof(reconnectProof->R1), reconnectProof->R3, true)) + { + ByteBuffer packet; + packet << uint8(AUTH_RECONNECT_PROOF); + packet << uint8(WOW_FAIL_VERSION_INVALID); + SendPacket(packet); + return true; + } + + // Sending response + ByteBuffer pkt; + pkt << uint8(AUTH_RECONNECT_PROOF); + pkt << uint8(WOW_SUCCESS); + pkt << uint16(0); // LoginFlags, 1 has account message + SendPacket(pkt); + _status = STATUS_AUTHED; + return true; + } + else + { + LOG_ERROR("server.authserver.hack", "'%s:%d' [ERROR] user %s tried to login, but session is invalid.", GetRemoteIpAddress().to_string().c_str(), + GetRemotePort(), _accountInfo.Login.c_str()); + return false; + } +} + +bool AuthSession::HandleRealmList() +{ + LOG_DEBUG("server.authserver", "Entering _HandleRealmList"); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS); + stmt->setUInt32(0, _accountInfo.Id); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1))); + _status = STATUS_WAITING_FOR_REALM_LIST; + return true; +} + +void AuthSession::RealmListCallback(PreparedQueryResult result) +{ + std::map<uint32, uint8> characterCounts; + if (result) + { + do + { + Field* fields = result->Fetch(); + characterCounts[fields[0].GetUInt32()] = fields[1].GetUInt8(); + } while (result->NextRow()); + } + + // Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm) + ByteBuffer pkt; + + size_t RealmListSize = 0; + for (auto const& [realmHandle, realm] : sRealmList->GetRealms()) + { + // don't work with realms which not compatible with the client + bool okBuild = ((_expversion & POST_BC_EXP_FLAG) && realm.Build == _build) || ((_expversion & PRE_BC_EXP_FLAG) && !AuthHelper::IsPreBCAcceptedClientBuild(realm.Build)); + + // No SQL injection. id of realm is controlled by the database. + uint32 flag = realm.Flags; + RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(realm.Build); + if (!okBuild) + { + if (!buildInfo) + continue; + + flag |= REALM_FLAG_OFFLINE | REALM_FLAG_SPECIFYBUILD; // tell the client what build the realm is for + } + + if (!buildInfo) + flag &= ~REALM_FLAG_SPECIFYBUILD; + + std::string name = realm.Name; + if (_expversion & PRE_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD) + { + std::ostringstream ss; + ss << name << " (" << buildInfo->MajorVersion << '.' << buildInfo->MinorVersion << '.' << buildInfo->BugfixVersion << ')'; + name = ss.str(); + } + + uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0; + + pkt << uint8(realm.Type); // realm type + if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients + pkt << uint8(lock); // if 1, then realm locked + + pkt << uint8(flag); // RealmFlags + pkt << name; + pkt << boost::lexical_cast<std::string>(realm.GetAddressForClient(GetRemoteIpAddress())); + pkt << float(realm.PopulationLevel); + pkt << uint8(characterCounts[realm.Id.Realm]); + pkt << uint8(realm.Timezone); // realm category + + if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients + pkt << uint8(realm.Id.Realm); + else + pkt << uint8(0x0); // 1.12.1 and 1.12.2 clients + + if (_expversion & POST_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD) + { + pkt << uint8(buildInfo->MajorVersion); + pkt << uint8(buildInfo->MinorVersion); + pkt << uint8(buildInfo->BugfixVersion); + pkt << uint16(buildInfo->Build); + } + + ++RealmListSize; + } + + if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients + { + pkt << uint8(0x10); + pkt << uint8(0x00); + } + else // 1.12.1 and 1.12.2 clients + { + pkt << uint8(0x00); + pkt << uint8(0x02); + } + + // make a ByteBuffer which stores the RealmList's size + ByteBuffer RealmListSizeBuffer; + RealmListSizeBuffer << uint32(0); + + if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients + RealmListSizeBuffer << uint16(RealmListSize); + else + RealmListSizeBuffer << uint32(RealmListSize); + + ByteBuffer hdr; + hdr << uint8(REALM_LIST); + hdr << uint16(pkt.size() + RealmListSizeBuffer.size()); + hdr.append(RealmListSizeBuffer); // append RealmList's size buffer + hdr.append(pkt); // append realms in the realmlist + SendPacket(hdr); + + _status = STATUS_AUTHED; +} + +bool AuthSession::VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect) +{ + if (!sConfigMgr->GetOption<bool>("StrictVersionCheck", false)) + return true; + + Acore::Crypto::SHA1::Digest zeros; + Acore::Crypto::SHA1::Digest const* versionHash = nullptr; + + if (!isReconnect) + { + RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(_build); + if (!buildInfo) + return false; + + if (_os == "Win") + versionHash = &buildInfo->WindowsHash; + else if (_os == "OSX") + versionHash = &buildInfo->MacHash; + + if (!versionHash) + return false; + + if (zeros == *versionHash) + return true; // not filled serverside + } + else + versionHash = &zeros; + + Acore::Crypto::SHA1 version; + version.UpdateData(a, aLength); + version.UpdateData(*versionHash); + version.Finalize(); + + return (versionProof == version.GetDigest()); +} diff --git a/src/server/authserver/Server/AuthSession.h b/src/server/authserver/Server/AuthSession.h new file mode 100644 index 0000000000..216a9c95cb --- /dev/null +++ b/src/server/authserver/Server/AuthSession.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + */ + +#ifndef __AUTHSESSION_H__ +#define __AUTHSESSION_H__ + +#include "AsyncCallbackProcessor.h" +#include "BigNumber.h" +#include "ByteBuffer.h" +#include "Common.h" +#include "CryptoHash.h" +#include "Optional.h" +#include "Socket.h" +#include "SRP6.h" +#include "QueryResult.h" +#include <memory> +#include <boost/asio/ip/tcp.hpp> + +using boost::asio::ip::tcp; + +class Field; +struct AuthHandler; + +enum AuthStatus +{ + STATUS_CHALLENGE = 0, + STATUS_LOGON_PROOF, + STATUS_RECONNECT_PROOF, + STATUS_AUTHED, + STATUS_WAITING_FOR_REALM_LIST, + STATUS_CLOSED +}; + +struct AccountInfo +{ + void LoadResult(Field* fields); + + uint32 Id = 0; + std::string Login; + bool IsLockedToIP = false; + std::string LockCountry; + std::string LastIP; + uint32 FailedLogins = 0; + bool IsBanned = false; + bool IsPermanentlyBanned = false; + AccountTypes SecurityLevel = SEC_PLAYER; +}; + +class AuthSession : public Socket<AuthSession> +{ + typedef Socket<AuthSession> AuthSocket; + +public: + static std::unordered_map<uint8, AuthHandler> InitHandlers(); + + AuthSession(tcp::socket&& socket); + + void Start() override; + bool Update() override; + + void SendPacket(ByteBuffer& packet); + +protected: + void ReadHandler() override; + +private: + bool HandleLogonChallenge(); + bool HandleLogonProof(); + bool HandleReconnectChallenge(); + bool HandleReconnectProof(); + bool HandleRealmList(); + + void CheckIpCallback(PreparedQueryResult result); + void LogonChallengeCallback(PreparedQueryResult result); + void ReconnectChallengeCallback(PreparedQueryResult result); + void RealmListCallback(PreparedQueryResult result); + + bool VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect); + + Optional<Acore::Crypto::SRP6> _srp6; + SessionKey _sessionKey = {}; + std::array<uint8, 16> _reconnectProof = {}; + + AuthStatus _status; + AccountInfo _accountInfo; + Optional<std::vector<uint8>> _totpSecret; + std::string _localizationName; + std::string _os; + std::string _ipCountry; + uint16 _build; + uint8 _expversion; + + QueryCallbackProcessor _queryProcessor; +}; + +#pragma pack(push, 1) + +struct AuthHandler +{ + AuthStatus status; + size_t packetSize; + bool (AuthSession::* handler)(); +}; + +#pragma pack(pop) + +#endif diff --git a/src/server/authserver/Server/AuthSocket.cpp b/src/server/authserver/Server/AuthSocket.cpp deleted file mode 100644 index baf9cd26e6..0000000000 --- a/src/server/authserver/Server/AuthSocket.cpp +++ /dev/null @@ -1,1137 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -#include "AuthSocket.h" -#include "AES.h" -#include "AuthCodes.h" -#include "ByteBuffer.h" -#include "Common.h" -#include "Config.h" -#include "CryptoGenerics.h" -#include "CryptoHash.h" -#include "CryptoRandom.h" -#include "DatabaseEnv.h" -#include "IPLocation.h" -#include "Log.h" -#include "RealmList.h" -#include "SecretMgr.h" -#include "TOTP.h" -#include "Threading.h" -#include "Util.h" -#include <algorithm> -#include <openssl/crypto.h> -#include <openssl/md5.h> -#include <sstream> - -#define ChunkSize 2048 - -enum eAuthCmd -{ - AUTH_LOGON_CHALLENGE = 0x00, - AUTH_LOGON_PROOF = 0x01, - AUTH_RECONNECT_CHALLENGE = 0x02, - AUTH_RECONNECT_PROOF = 0x03, - REALM_LIST = 0x10, - XFER_INITIATE = 0x30, - XFER_DATA = 0x31, - XFER_ACCEPT = 0x32, - XFER_RESUME = 0x33, - XFER_CANCEL = 0x34 -}; - -// GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push, N), also any gcc version not support it at some paltform -#if defined(__GNUC__) -#pragma pack(1) -#else -#pragma pack(push, 1) -#endif - -typedef struct AUTH_LOGON_CHALLENGE_C -{ - uint8 cmd; - uint8 error; - uint16 size; - uint8 gamename[4]; - uint8 version1; - uint8 version2; - uint8 version3; - uint16 build; - uint8 platform[4]; - uint8 os[4]; - uint8 country[4]; - uint32 timezone_bias; - uint32 ip; - uint8 I_len; - uint8 I[1]; -} sAuthLogonChallenge_C; - -typedef struct AUTH_LOGON_PROOF_C -{ - uint8 cmd; - Acore::Crypto::SRP6::EphemeralKey A; - Acore::Crypto::SHA1::Digest clientM; - Acore::Crypto::SHA1::Digest crc_hash; - uint8 number_of_keys; - uint8 securityFlags; // 0x00-0x04 -} sAuthLogonProof_C; - -typedef struct AUTH_LOGON_PROOF_S -{ - uint8 cmd; - uint8 error; - Acore::Crypto::SHA1::Digest M2; - uint32 unk1; - uint32 unk2; - uint16 unk3; -} sAuthLogonProof_S; - -typedef struct AUTH_LOGON_PROOF_S_OLD -{ - uint8 cmd; - uint8 error; - Acore::Crypto::SHA1::Digest M2; - uint32 unk2; -} sAuthLogonProof_S_Old; - -typedef struct AUTH_RECONNECT_PROOF_C -{ - uint8 cmd; - uint8 R1[16]; - Acore::Crypto::SHA1::Digest R2, R3; - uint8 number_of_keys; -} sAuthReconnectProof_C; - -typedef struct XFER_INIT -{ - uint8 cmd; // XFER_INITIATE - uint8 fileNameLen; // strlen(fileName); - uint8 fileName[5]; // fileName[fileNameLen] - uint64 file_size; // file size (bytes) - uint8 md5[MD5_DIGEST_LENGTH]; // MD5 -} XFER_INIT; - -typedef struct XFER_DATA -{ - uint8 opcode; - uint16 data_size; - uint8 data[ChunkSize]; -} XFER_DATA_STRUCT; - -typedef struct AuthHandler -{ - eAuthCmd cmd; - uint32 status; - bool (AuthSocket::*handler)(); -} AuthHandler; - -// GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some paltform -#if defined(__GNUC__) -#pragma pack() -#else -#pragma pack(pop) -#endif - -// Launch a thread to transfer a patch to the client -class PatcherRunnable: public Acore::Runnable -{ -public: - PatcherRunnable(class AuthSocket*); - void run() override; - -private: - AuthSocket* mySocket; -}; - -typedef struct PATCH_INFO -{ - uint8 md5[MD5_DIGEST_LENGTH]; -} PATCH_INFO; - -// Caches MD5 hash of client patches present on the server -class Patcher -{ -public: - typedef std::map<std::string, PATCH_INFO*> Patches; - ~Patcher(); - Patcher(); - [[nodiscard]] Patches::const_iterator begin() const { return _patches.begin(); } - [[nodiscard]] Patches::const_iterator end() const { return _patches.end(); } - void LoadPatchMD5(char*); - bool GetHash(char* pat, uint8 mymd5[16]); - -private: - void LoadPatchesInfo(); - Patches _patches; -}; - -std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } }; - -const AuthHandler table[] = -{ - { AUTH_LOGON_CHALLENGE, STATUS_CHALLENGE, &AuthSocket::_HandleLogonChallenge }, - { AUTH_LOGON_PROOF, STATUS_LOGON_PROOF, &AuthSocket::_HandleLogonProof }, - { AUTH_RECONNECT_CHALLENGE, STATUS_CHALLENGE, &AuthSocket::_HandleReconnectChallenge }, - { AUTH_RECONNECT_PROOF, STATUS_RECON_PROOF, &AuthSocket::_HandleReconnectProof }, - { REALM_LIST, STATUS_AUTHED, &AuthSocket::_HandleRealmList }, - { XFER_ACCEPT, STATUS_PATCH, &AuthSocket::_HandleXferAccept }, - { XFER_RESUME, STATUS_PATCH, &AuthSocket::_HandleXferResume }, - { XFER_CANCEL, STATUS_PATCH, &AuthSocket::_HandleXferCancel } -}; - -#define AUTH_TOTAL_COMMANDS 8 - -void AccountInfo::LoadResult(Field* fields) -{ - // 0 1 2 3 4 5 - // SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, - // 6 7 - // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, - // 8 9 - // ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate, - // 10 - // aa.gmlevel (, more query-specific fields) - // FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 LEFT JOIN ip_banned ipb ON ipb.ip = ? WHERE a.username = ? - - Id = fields[0].GetUInt32(); - Login = fields[1].GetString(); - IsLockedToIP = fields[2].GetBool(); - LockCountry = fields[3].GetString(); - LastIP = fields[4].GetString(); - FailedLogins = fields[5].GetUInt32(); - IsBanned = fields[6].GetBool() || fields[8].GetBool(); - IsPermanentlyBanned = fields[7].GetBool() || fields[9].GetBool(); - SecurityLevel = static_cast<AccountTypes>(fields[10].GetUInt8()) > SEC_CONSOLE ? SEC_CONSOLE : static_cast<AccountTypes>(fields[10].GetUInt8()); - - // Use our own uppercasing of the account name instead of using UPPER() in mysql query - // This is how the account was created in the first place and changing it now would result in breaking - // login for all accounts having accented characters in their name - Utf8ToUpperOnlyLatin(Login); -} - -// Holds the MD5 hash of client patches present on the server -Patcher PatchesCache; - -// Constructor - set the N and g values for SRP6 -AuthSocket::AuthSocket(RealmSocket& socket) : - pPatch(nullptr), socket_(socket), _status(STATUS_CHALLENGE), _build(0), - _expversion(0) -{ -} - -// Close patch file descriptor before leaving -AuthSocket::~AuthSocket() = default; - -// Accept the connection -void AuthSocket::OnAccept() -{ - LOG_INFO("server.authserver", "'%s:%d' Accepting connection", socket().getRemoteAddress().c_str(), socket().getRemotePort()); -} - -void AuthSocket::OnClose() -{ - LOG_DEBUG("server.authserver", "AuthSocket::OnClose"); -} - -// Read the packet from the client -void AuthSocket::OnRead() -{ -#define MAX_AUTH_LOGON_CHALLENGES_IN_A_ROW 3 - uint32 challengesInARow = 0; - -#define MAX_AUTH_GET_REALM_LIST 10 - uint32 challengesInARowRealmList = 0; - - uint8 _cmd; - while (true) - { - if (!socket().recv_soft((char*)&_cmd, 1)) - return; - - if (_cmd == AUTH_LOGON_CHALLENGE) - { - ++challengesInARow; - if (challengesInARow == MAX_AUTH_LOGON_CHALLENGES_IN_A_ROW) - { - LOG_INFO("server.authserver", "Got %u AUTH_LOGON_CHALLENGE in a row from '%s', possible ongoing DoS", challengesInARow, socket().getRemoteAddress().c_str()); - socket().shutdown(); - return; - } - } - else if (_cmd == REALM_LIST) - { - challengesInARowRealmList++; - if (challengesInARowRealmList == MAX_AUTH_GET_REALM_LIST) - { - LOG_INFO("server.authserver", "Got %u REALM_LIST in a row from '%s', possible ongoing DoS", challengesInARowRealmList, socket().getRemoteAddress().c_str()); - socket().shutdown(); - return; - } - } - - size_t i; - - // Circle through known commands and call the correct command handler - for (i = 0; i < AUTH_TOTAL_COMMANDS; ++i) - { - if ((uint8)table[i].cmd == _cmd && (table[i].status == _status)) - { - LOG_DEBUG("server.authserver", "Got data for cmd %u recv length %u", (uint32)_cmd, (uint32)socket().recv_len()); - - if (!(*this.*table[i].handler)()) - { - LOG_DEBUG("server.authserver", "Command handler failed for cmd %u recv length %u", (uint32)_cmd, (uint32)socket().recv_len()); - return; - } - break; - } - } - - // Report unknown packets in the error log - if (i == AUTH_TOTAL_COMMANDS) - { - LOG_DEBUG("server.authserver", "Got unknown packet from '%s'", socket().getRemoteAddress().c_str()); - socket().shutdown(); - return; - } - } -} - -std::map<std::string, uint32> LastLoginAttemptTimeForIP; -uint32 LastLoginAttemptCleanTime = 0; -std::mutex LastLoginAttemptMutex; - -// Logon Challenge command handler -bool AuthSocket::_HandleLogonChallenge() -{ - LOG_DEBUG("server.authserver", "Entering _HandleLogonChallenge"); - if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) - return false; - - ///- Session is closed unless overriden - _status = STATUS_CLOSED; - - // Read the first 4 bytes (header) to get the length of the remaining of the packet - std::vector<uint8> buf; - buf.resize(4); - - socket().recv((char*)&buf[0], 4); - - EndianConvertPtr<uint16>(&buf[0]); - - uint16 remaining = ((sAuthLogonChallenge_C*)&buf[0])->size; - LOG_DEBUG("server.authserver", "[AuthChallenge] got header, body is %#04x bytes", remaining); - - if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) - return false; - - //No big fear of memory outage (size is int16, i.e. < 65536) - buf.resize(remaining + buf.size() + 1); - buf[buf.size() - 1] = 0; - sAuthLogonChallenge_C* ch = (sAuthLogonChallenge_C*)&buf[0]; - - // Read the remaining of the packet - socket().recv((char*)&buf[4], remaining); - LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", ch->size); - LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); - - // BigEndian code, nop in little endian case - // size already converted - EndianConvertPtr<uint32>(&ch->gamename[0]); - EndianConvert(ch->build); - EndianConvertPtr<uint32>(&ch->platform[0]); - EndianConvertPtr<uint32>(&ch->os[0]); - EndianConvertPtr<uint32>(&ch->country[0]); - EndianConvert(ch->timezone_bias); - EndianConvert(ch->ip); - - std::string login((char const*)ch->I, ch->I_len); - LOG_DEBUG("server.authserver", "[AuthChallenge] '%s'", login.c_str()); - - _build = ch->build; - _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); - _os = (const char*)ch->os; - - if (_os.size() > 4) - return false; - - // Restore string order as its byte order is reversed - std::reverse(_os.begin(), _os.end()); - - _localizationName.resize(4); - for (int i = 0; i < 4; ++i) - _localizationName[i] = ch->country[4 - i - 1]; - - ByteBuffer pkt; - pkt << uint8(AUTH_LOGON_CHALLENGE); - pkt << uint8(0x00); - - auto SendAuthPacket = [&]() - { - socket().send((char const*)pkt.contents(), pkt.size()); - }; - - // Verify that this IP is not in the ip_banned table - LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); - - std::string const& ipAddress = socket().getRemoteAddress(); - uint32 port = socket().getRemotePort(); - - // Get the account details from the account table - // No SQL injection (prepared statement) - auto stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); - stmt->setString(0, ipAddress); - stmt->setString(1, login); - - PreparedQueryResult res2 = LoginDatabase.Query(stmt); - if (!res2) //no account - { - pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); - SendAuthPacket(); - return true; - } - - Field* fields = res2->Fetch(); - - _accountInfo.LoadResult(fields); - - // If the IP is 'locked', check that the player comes indeed from the correct IP address - if (_accountInfo.IsLockedToIP) - { - LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s' is logging in from '%s'", _accountInfo.Login.c_str(), _accountInfo.LastIP.c_str(), ipAddress.c_str()); - - if (_accountInfo.LastIP != ipAddress) - { - LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP differs"); - pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); - SendAuthPacket(); - return true; - } - } - else - { - if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ipAddress)) - _ipCountry = location->CountryCode; - - LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _accountInfo.Login.c_str()); - if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00") - LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _accountInfo.Login.c_str()); - else if (!_ipCountry.empty()) - { - LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _accountInfo.Login.c_str(), _accountInfo.LockCountry.c_str(), _ipCountry.c_str()); - if (_ipCountry != _accountInfo.LockCountry) - { - pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); - SendAuthPacket(); - return true; - } - } - } - - // If the account is banned, reject the logon attempt - if (_accountInfo.IsBanned) - { - if (_accountInfo.IsPermanentlyBanned) - { - pkt << uint8(WOW_FAIL_BANNED); - LOG_DEBUG("server.authserver.banned", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); - } - else - { - pkt << uint8(WOW_FAIL_SUSPENDED); - LOG_DEBUG("server.authserver.banned", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); - } - - SendAuthPacket(); - return true; - } - - uint8 securityFlags = 0; - - // Check if a TOTP token is needed - if (sConfigMgr->GetOption<bool>("EnableTOTP", false) && !fields[11].IsNull()) - { - LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' using TOTP", _accountInfo.Login.c_str()); - - securityFlags = 4; - _totpSecret = fields[11].GetBinary(); - if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY)) - { - bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(*_totpSecret, *secret); - if (!success) - { - pkt << uint8(WOW_FAIL_DB_BUSY); - LOG_ERROR("server.authserver", "[AuthChallenge] Account '%s' has invalid ciphertext for TOTP token key stored", _accountInfo.Login.c_str()); - SendAuthPacket(); - return true; - } - } - } - - _srp6.emplace( - _accountInfo.Login, - fields[12].GetBinary<Acore::Crypto::SRP6::SALT_LENGTH>(), - fields[13].GetBinary<Acore::Crypto::SRP6::VERIFIER_LENGTH>()); - - // Fill the response packet with the result - if (!AuthHelper::IsAcceptedClientBuild(_build)) - { - pkt << uint8(WOW_FAIL_VERSION_INVALID); - SendAuthPacket(); - return true; - } - - pkt << uint8(WOW_SUCCESS); - - // B may be calculated < 32B so we force minimal length to 32B - pkt.append(_srp6->B); - pkt << uint8(1); - pkt.append(_srp6->g); - pkt << uint8(32); - pkt.append(_srp6->N); - pkt.append(_srp6->s); - pkt.append(VersionChallenge.data(), VersionChallenge.size()); - pkt << uint8(securityFlags); // security flags (0x0...0x04) - - if (securityFlags & 0x01) // PIN input - { - pkt << uint32(0); - pkt << uint64(0) << uint64(0); // 16 bytes hash? - } - - if (securityFlags & 0x02) // Matrix input - { - pkt << uint8(0); - pkt << uint8(0); - pkt << uint8(0); - pkt << uint8(0); - pkt << uint64(0); - } - - if (securityFlags & 0x04) // Security token input - pkt << uint8(1); - - LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using locale (%u)", - ipAddress.c_str(), port, _accountInfo.Login.c_str(), GetLocaleByName(_localizationName)); - - ///- All good, await client's proof - _status = STATUS_LOGON_PROOF; - - SendAuthPacket(); - return true; -} - -// Logon Proof command handler -bool AuthSocket::_HandleLogonProof() -{ - LOG_TRACE("server.authserver", "Entering _HandleLogonProof"); - - // Read the packet - sAuthLogonProof_C lp; - - if (!socket().recv((char*)&lp, sizeof(sAuthLogonProof_C))) - { - return false; - } - - _status = STATUS_CLOSED; - - // If the client has no valid version - if (_expversion == NO_VALID_EXP_FLAG) - { - // Check if we have the appropriate patch on the disk - LOG_DEBUG("server.authserver", "Client with invalid version, patching is not implemented"); - socket().shutdown(); - return true; - } - - if (std::optional<SessionKey> K = _srp6->VerifyChallengeResponse(lp.A, lp.clientM)) - { - _sessionKey = *K; - LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str()); - - // Check auth token - bool tokenSuccess = false; - bool sentToken = (lp.securityFlags & 0x04); - - if (sentToken && _totpSecret) - { - uint8 size; - socket().recv((char*)&size, 1); - char* token = new char[size + 1]; - token[size] = '\0'; - socket().recv(token, size); - unsigned int incomingToken = atoi(token); - delete[] token; - - tokenSuccess = Acore::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken); - memset(_totpSecret->data(), 0, _totpSecret->size()); - } - else if (!sentToken && !_totpSecret) - tokenSuccess = true; - - if (!tokenSuccess) - { - LOG_DEBUG("server.authsrver", "[AuthChallenge] account %s failed token", _accountInfo.Login.c_str()); - char data[4] = { AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0 }; - socket().send(data, sizeof(data)); - } - - LOG_DEBUG("network", "'%s:%d' User '%s' successfully authenticated", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str()); - - // Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account - // No SQL injection (escaped user name) and IP address as received by socket - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF); - stmt->setBinary(0, _sessionKey); - stmt->setString(1, socket().getRemoteAddress().c_str()); - stmt->setUInt32(2, GetLocaleByName(_localizationName)); - stmt->setString(3, _os); - stmt->setString(4, _accountInfo.Login); - LoginDatabase.DirectExecute(stmt); - - // Finish SRP6 and send the final result to the client - Acore::Crypto::SHA1::Digest M2 = Acore::Crypto::SRP6::GetSessionVerifier(lp.A, lp.clientM, _sessionKey); - - if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients - { - sAuthLogonProof_S proof; - proof.M2 = M2; - proof.cmd = AUTH_LOGON_PROOF; - proof.error = 0; - proof.unk1 = 0x00800000; // Accountflags. 0x01 = GM, 0x08 = Trial, 0x00800000 = Pro pass (arena tournament) - proof.unk2 = 0x00; // SurveyId - proof.unk3 = 0x00; // 0x1 = has account message - socket().send((char*)&proof, sizeof(proof)); - } - else - { - sAuthLogonProof_S_Old proof; - proof.M2 = M2; - proof.cmd = AUTH_LOGON_PROOF; - proof.error = 0; - proof.unk2 = 0x00; - socket().send((char*)&proof, sizeof(proof)); - } - - ///- Set _status to authed! - _status = STATUS_AUTHED; - } - else - { - char data[4] = { AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0 }; - socket().send(data, sizeof(data)); - - LOG_INFO("server.authserver.hack", "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!", - socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str()); - - uint32 MaxWrongPassCount = sConfigMgr->GetOption<int32>("WrongPass.MaxCount", 0); - - // We can not include the failed account login hook. However, this is a workaround to still log this. - if (sConfigMgr->GetOption<bool>("WrongPass.Logging", false)) - { - LoginDatabasePreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING); - logstmt->setString(0, _accountInfo.Login); - logstmt->setString(1, socket().getRemoteAddress()); - logstmt->setString(2, "Logged on failed AccountLogin due wrong password"); - - LoginDatabase.Execute(logstmt); - } - - if (MaxWrongPassCount > 0) - { - //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS); - stmt->setString(0, _accountInfo.Login); - LoginDatabase.Execute(stmt); - - if (++_accountInfo.FailedLogins >= MaxWrongPassCount) - { - uint32 WrongPassBanTime = sConfigMgr->GetOption<int32>("WrongPass.BanTime", 600); - bool WrongPassBanType = sConfigMgr->GetOption<bool>("WrongPass.BanType", false); - - if (WrongPassBanType) - { - stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED); - stmt->setUInt32(0, _accountInfo.Id); - stmt->setUInt32(1, WrongPassBanTime); - LoginDatabase.Execute(stmt); - - LOG_DEBUG("network", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times", - socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str(), WrongPassBanTime, _accountInfo.FailedLogins); - } - else - { - stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); - stmt->setString(0, socket().getRemoteAddress()); - stmt->setUInt32(1, WrongPassBanTime); - LoginDatabase.Execute(stmt); - - LOG_DEBUG("network", "'%s:%d' [AuthChallenge] IP %s got banned for '%u' seconds because account %s failed to authenticate '%u' times", - socket().getRemoteAddress().c_str(), socket().getRemotePort(), socket().getRemoteAddress().c_str(), WrongPassBanTime, _accountInfo.Login.c_str(), _accountInfo.FailedLogins); - } - } - } - } - - return true; -} - -// Reconnect Challenge command handler -bool AuthSocket::_HandleReconnectChallenge() -{ - LOG_TRACE("server.authserver", "Entering _HandleReconnectChallenge"); - - if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) - return false; - - // Read the first 4 bytes (header) to get the length of the remaining of the packet - std::vector<uint8> buf; - buf.resize(4); - - socket().recv((char*)&buf[0], 4); - - EndianConvertPtr<uint16>(&buf[0]); - - uint16 remaining = ((sAuthLogonChallenge_C*)&buf[0])->size; - LOG_DEBUG("server.authserver", "[ReconnectChallenge] got header, body is %#04x bytes", remaining); - - if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) - return false; - - ///- Session is closed unless overriden - _status = STATUS_CLOSED; - - // No big fear of memory outage (size is int16, i.e. < 65536) - buf.resize(remaining + buf.size() + 1); - buf[buf.size() - 1] = 0; - sAuthLogonChallenge_C* ch = (sAuthLogonChallenge_C*)&buf[0]; - - // Read the remaining of the packet - socket().recv((char*)&buf[4], remaining); - LOG_DEBUG("server.authserver", "[ReconnectChallenge] got full packet, %#04x bytes", ch->size); - LOG_DEBUG("server.authserver", "[ReconnectChallenge] name(%d): '%s'", ch->I_len, ch->I); - - std::string login((char const*)ch->I, ch->I_len); - LOG_DEBUG("server.authserver", "[ReconnectChallenge] '%s'", login.c_str()); - - // Reinitialize build, expansion and the account securitylevel - _build = ch->build; - _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); - _os = (const char*)ch->os; - - if (_os.size() > 4) - return false; - - auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE); - stmt->setString(0, socket().getRemoteAddress()); - stmt->setString(1, login); - PreparedQueryResult result = LoginDatabase.Query(stmt); - - // Stop if the account is not found - if (!result) - { - LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login and we cannot find his session key in the database.", socket().getRemoteAddress().c_str(), socket().getRemotePort(), login.c_str()); - socket().shutdown(); - return false; - } - - Field* fields = result->Fetch(); - _accountInfo.LoadResult(fields); - - // Restore string order as its byte order is reversed - std::reverse(_os.begin(), _os.end()); - - _sessionKey = fields[11].GetBinary<SESSION_KEY_LENGTH>(); - Acore::Crypto::GetRandomBytes(_reconnectProof); - - ///- All good, await client's proof - _status = STATUS_RECON_PROOF; - - // Sending response - ByteBuffer pkt; - pkt << uint8(AUTH_RECONNECT_CHALLENGE); - pkt << uint8(WOW_SUCCESS); - pkt.append(_reconnectProof); // 16 bytes random - pkt.append(VersionChallenge.data(), VersionChallenge.size()); - socket().send((char const*)pkt.contents(), pkt.size()); - return true; -} - -// Reconnect Proof command handler -bool AuthSocket::_HandleReconnectProof() -{ - LOG_TRACE("server.authserver", "Entering _HandleReconnectProof"); - // Read the packet - sAuthReconnectProof_C lp; - if (!socket().recv((char*)&lp, sizeof(sAuthReconnectProof_C))) - return false; - - _status = STATUS_CLOSED; - - if (_accountInfo.Login.empty()) - return false; - - BigNumber t1; - t1.SetBinary(lp.R1, 16); - - Acore::Crypto::SHA1 sha; - sha.UpdateData(_accountInfo.Login); - sha.UpdateData(t1.ToByteArray<16>()); - sha.UpdateData(_reconnectProof); - sha.UpdateData(_sessionKey); - sha.Finalize(); - - if (sha.GetDigest() == lp.R2) - { - // Sending response - ByteBuffer pkt; - pkt << uint8(AUTH_RECONNECT_PROOF); - pkt << uint8(0x00); - pkt << uint16(0x00); // 2 bytes zeros - socket().send((char const*)pkt.contents(), pkt.size()); - - ///- Set _status to authed! - _status = STATUS_AUTHED; - - return true; - } - else - { - LOG_ERROR("server.authserver.hack", "'%s:%d' [ERROR] user %s tried to login, but session is invalid.", - socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str()); - socket().shutdown(); - return false; - } -} - -ACE_INET_Addr const& AuthSocket::GetAddressForClient(Realm const& realm, ACE_INET_Addr const& clientAddr) -{ - // Attempt to send best address for client - if (clientAddr.is_loopback()) - { - // Try guessing if realm is also connected locally - if (realm.LocalAddress->is_loopback() || realm.ExternalAddress->is_loopback()) - return clientAddr; - - // Assume that user connecting from the machine that authserver is located on - // has all realms available in his local network - return *realm.LocalAddress; - } - - // Check if connecting client is in the same network - if (IsIPAddrInNetwork(*realm.LocalAddress, clientAddr, *realm.LocalSubnetMask)) - { - return *realm.LocalAddress; - } - - // Return external IP - return *realm.ExternalAddress; -} - -// Realm List command handler -bool AuthSocket::_HandleRealmList() -{ - LOG_TRACE("server.authserver", "Entering _HandleRealmList"); - if (socket().recv_len() < 5) - return false; - - socket().recv_skip(5); - - // Get the user id (else close the connection) - // No SQL injection (prepared statement) - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME); - stmt->setString(0, _accountInfo.Login); - - PreparedQueryResult result = LoginDatabase.Query(stmt); - if (!result) - { - LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login but we cannot find him in the database.", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str()); - socket().shutdown(); - return false; - } - - Field* fields = result->Fetch(); - uint32 id = fields[0].GetUInt32(); - - // Update realm list if need - sRealmList->UpdateIfNeed(); - - ACE_INET_Addr clientAddr; - socket().peer().get_remote_addr(clientAddr); - - // Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm) - ByteBuffer pkt; - size_t RealmListSize = 0; - - for (auto& [realmHandle, realm] : sRealmList->GetRealms()) - { - // don't work with realms which not compatible with the client - bool okBuild = ((_expversion & POST_BC_EXP_FLAG) && realm.Build == _build) || ((_expversion & PRE_BC_EXP_FLAG) && !AuthHelper::IsPreBCAcceptedClientBuild(realm.Build)); - - // No SQL injection. id of realm is controlled by the database. - uint32 flag = realm.Flags; - - RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(realm.Build); - if (!okBuild) - { - if (!buildInfo) - continue; - - flag |= REALM_FLAG_OFFLINE | REALM_FLAG_SPECIFYBUILD; // tell the client what build the realm is for - } - - if (!buildInfo) - flag &= ~REALM_FLAG_SPECIFYBUILD; - - std::string name = realm.Name; - if (_expversion & PRE_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD) - { - std::ostringstream ss; - ss << name << " (" << buildInfo->MajorVersion << '.' << buildInfo->MinorVersion << '.' << buildInfo->BugfixVersion << ')'; - name = ss.str(); - } - - // We don't need the port number from which client connects with but the realm's port - clientAddr.set_port_number(realm.ExternalAddress->get_port_number()); - - uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0; - - uint8 AmountOfCharacters = 0; - stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_NUM_CHARS_ON_REALM); - stmt->setUInt32(0, realm.Id.Realm); - stmt->setUInt32(1, id); - result = LoginDatabase.Query(stmt); - if (result) - AmountOfCharacters = (*result)[0].GetUInt8(); - - pkt << realm.Type; // realm type - if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients - pkt << lock; // if 1, then realm locked - pkt << uint8(flag); // RealmFlags - pkt << name; - pkt << GetAddressString(GetAddressForClient(realm, clientAddr)); - pkt << realm.PopulationLevel; - pkt << AmountOfCharacters; - pkt << realm.Timezone; // realm category - if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients - pkt << uint8(realm.Id.Realm); - else - pkt << uint8(0x0); // 1.12.1 and 1.12.2 clients - - if (_expversion & POST_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD) - { - pkt << uint8(buildInfo->MajorVersion); - pkt << uint8(buildInfo->MinorVersion); - pkt << uint8(buildInfo->BugfixVersion); - pkt << uint16(buildInfo->Build); - } - - ++RealmListSize; - } - - if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients - { - pkt << uint8(0x10); - pkt << uint8(0x00); - } - else // 1.12.1 and 1.12.2 clients - { - pkt << uint8(0x00); - pkt << uint8(0x02); - } - - // make a ByteBuffer which stores the RealmList's size - ByteBuffer RealmListSizeBuffer; - RealmListSizeBuffer << uint32(0); - if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients - RealmListSizeBuffer << uint16(RealmListSize); - else - RealmListSizeBuffer << uint32(RealmListSize); - - ByteBuffer hdr; - hdr << uint8(REALM_LIST); - hdr << uint16(pkt.size() + RealmListSizeBuffer.size()); - hdr.append(RealmListSizeBuffer); // append RealmList's size buffer - hdr.append(pkt); // append realms in the realmlist - - socket().send((char const*)hdr.contents(), hdr.size()); - - return true; -} - -// Resume patch transfer -bool AuthSocket::_HandleXferResume() -{ - LOG_TRACE("server.authserver", "Entering _HandleXferResume"); - // Check packet length and patch existence - if (socket().recv_len() < 9 || !pPatch) // FIXME: pPatch is never used - { - LOG_ERROR("server.authserver", "Error while resuming patch transfer (wrong packet)"); - return false; - } - - // Launch a PatcherRunnable thread starting at given patch file offset - uint64 start; - socket().recv_skip(1); - socket().recv((char*)&start, sizeof(start)); - fseek(pPatch, long(start), 0); - - Acore::Thread u(new PatcherRunnable(this)); - return true; -} - -// Cancel patch transfer -bool AuthSocket::_HandleXferCancel() -{ - LOG_DEBUG("server.authserver", "Entering _HandleXferCancel"); - - // Close and delete the socket - socket().recv_skip(1); //clear input buffer - socket().shutdown(); - - return true; -} - -// Accept patch transfer -bool AuthSocket::_HandleXferAccept() -{ - LOG_DEBUG("server.authserver", "Entering _HandleXferAccept"); - - // Check packet length and patch existence - if (!pPatch) - { - LOG_ERROR("server.authserver", "Error while accepting patch transfer (wrong packet)"); - return false; - } - - // Launch a PatcherRunnable thread, starting at the beginning of the patch file - socket().recv_skip(1); // clear input buffer - fseek(pPatch, 0, 0); - - Acore::Thread u(new PatcherRunnable(this)); - return true; -} - -PatcherRunnable::PatcherRunnable(class AuthSocket* as) -{ - mySocket = as; -} - -// Send content of patch file to the client -void PatcherRunnable::run() { } - -// Preload MD5 hashes of existing patch files on server -#ifndef _WIN32 -#include <cerrno> -#include <dirent.h> -void Patcher::LoadPatchesInfo() -{ - DIR* dirp; - struct dirent* dp; - dirp = opendir("./patches/"); - - if (!dirp) - return; - - while (dirp) - { - errno = 0; - if ((dp = readdir(dirp)) != nullptr) - { - int l = strlen(dp->d_name); - - if (l < 8) - continue; - - if (!memcmp(&dp->d_name[l - 4], ".mpq", 4)) - LoadPatchMD5(dp->d_name); - } - else - { - if (errno != 0) - { - closedir(dirp); - return; - } - break; - } - } - - if (dirp) - closedir(dirp); -} -#else -void Patcher::LoadPatchesInfo() -{ - WIN32_FIND_DATA fil; - HANDLE hFil = FindFirstFile("./patches/*.mpq", &fil); - if (hFil == INVALID_HANDLE_VALUE) - return; // no patches were found - - do - LoadPatchMD5(fil.cFileName); - while (FindNextFile(hFil, &fil)); -} -#endif - -// Calculate and store MD5 hash for a given patch file -void Patcher::LoadPatchMD5(char* szFileName) -{ - // Try to open the patch file - std::string path = "./patches/"; - path += szFileName; - FILE* pPatch = fopen(path.c_str(), "rb"); - LOG_DEBUG("server.authserver", "Loading patch info from %s\n", path.c_str()); - - if (!pPatch) - { - LOG_ERROR("server.authserver", "Error loading patch %s\n", path.c_str()); - return; - } - - // Calculate the MD5 hash - MD5_CTX ctx; - MD5_Init(&ctx); - uint8* buf = new uint8[512 * 1024]; - - while (!feof(pPatch)) - { - size_t read = fread(buf, 1, 512 * 1024, pPatch); - MD5_Update(&ctx, buf, read); - } - - delete [] buf; - fclose(pPatch); - - // Store the result in the internal patch hash map - _patches[path] = new PATCH_INFO; - MD5_Final((uint8*)&_patches[path]->md5, &ctx); -} - -// Get cached MD5 hash for a given patch file -bool Patcher::GetHash(char* pat, uint8 mymd5[16]) -{ - for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) - if (!stricmp(pat, i->first.c_str())) - { - memcpy(mymd5, i->second->md5, 16); - return true; - } - - return false; -} - -// Launch the patch hashing mechanism on object creation -Patcher::Patcher() -{ - LoadPatchesInfo(); -} - -// Empty and delete the patch map on termination -Patcher::~Patcher() -{ - for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i) - delete i->second; -} diff --git a/src/server/authserver/Server/AuthSocket.h b/src/server/authserver/Server/AuthSocket.h deleted file mode 100644 index 5b8e91bbf5..0000000000 --- a/src/server/authserver/Server/AuthSocket.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -#ifndef _AUTHSOCKET_H -#define _AUTHSOCKET_H - -#include "Common.h" -#include "CryptoHash.h" -#include "Optional.h" -#include "RealmSocket.h" -#include "SRP6.h" -#include <mutex> - -class ACE_INET_Addr; -class Field; -struct Realm; - -enum eStatus -{ - STATUS_CHALLENGE, - STATUS_LOGON_PROOF, - STATUS_RECON_PROOF, - STATUS_PATCH, // unused - STATUS_AUTHED, - STATUS_CLOSED -}; - -struct AccountInfo -{ - void LoadResult(Field* fields); - - uint32 Id = 0; - std::string Login; - bool IsLockedToIP = false; - std::string LockCountry; - std::string LastIP; - uint32 FailedLogins = 0; - bool IsBanned = false; - bool IsPermanentlyBanned = false; - AccountTypes SecurityLevel = SEC_PLAYER; -}; - -// Handle login commands -class AuthSocket: public RealmSocket::Session -{ -public: - const static int s_BYTE_SIZE = 32; - - AuthSocket(RealmSocket& socket); - ~AuthSocket() override; - - void OnRead() override; - void OnAccept() override; - void OnClose() override; - - static ACE_INET_Addr const& GetAddressForClient(Realm const& realm, ACE_INET_Addr const& clientAddr); - - bool _HandleLogonChallenge(); - bool _HandleLogonProof(); - bool _HandleReconnectChallenge(); - bool _HandleReconnectProof(); - bool _HandleRealmList(); - - //data transfer handle for patch - bool _HandleXferResume(); - bool _HandleXferCancel(); - bool _HandleXferAccept(); - - FILE* pPatch; - std::mutex patcherLock; - -private: - RealmSocket& socket_; - RealmSocket& socket() { return socket_; } - - std::optional<Acore::Crypto::SRP6> _srp6; - SessionKey _sessionKey = {}; - std::array<uint8, 16> _reconnectProof = {}; - - eStatus _status; - - AccountInfo _accountInfo; - Optional<std::vector<uint8>> _totpSecret; - - // Since GetLocaleByName() is _NOT_ bijective, we have to store the locale as a string. Otherwise we can't differ - // between enUS and enGB, which is important for the patch system - std::string _localizationName; - std::string _os; - std::string _ipCountry; - uint16 _build; - uint8 _expversion; -}; - -#endif diff --git a/src/server/authserver/Server/AuthSocketMgr.h b/src/server/authserver/Server/AuthSocketMgr.h new file mode 100644 index 0000000000..5fed06c880 --- /dev/null +++ b/src/server/authserver/Server/AuthSocketMgr.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + */ + +#ifndef AuthSocketMgr_h__ +#define AuthSocketMgr_h__ + +#include "SocketMgr.h" +#include "AuthSession.h" + +class AuthSocketMgr : public SocketMgr<AuthSession> +{ + typedef SocketMgr<AuthSession> BaseSocketMgr; + +public: + static AuthSocketMgr& Instance() + { + static AuthSocketMgr instance; + return instance; + } + + bool StartNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override + { + if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount)) + return false; + + _acceptor->AsyncAcceptWithCallback<&AuthSocketMgr::OnSocketAccept>(); + return true; + } + +protected: + NetworkThread<AuthSession>* CreateThreads() const override + { + return new NetworkThread<AuthSession>[1]; + } + + static void OnSocketAccept(tcp::socket&& sock, uint32 threadIndex) + { + Instance().OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex); + } +}; + +#define sAuthSocketMgr AuthSocketMgr::Instance() + +#endif // AuthSocketMgr_h__ diff --git a/src/server/authserver/Server/RealmAcceptor.h b/src/server/authserver/Server/RealmAcceptor.h deleted file mode 100644 index f11c3b81fb..0000000000 --- a/src/server/authserver/Server/RealmAcceptor.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -#ifndef __REALMACCEPTOR_H__ -#define __REALMACCEPTOR_H__ - -#include <ace/Acceptor.h> -#include <ace/SOCK_Acceptor.h> - -#include "RealmSocket.h" -#include "AuthSocket.h" - -class RealmAcceptor : public ACE_Acceptor<RealmSocket, ACE_SOCK_Acceptor> -{ -public: - RealmAcceptor() = default; - virtual ~RealmAcceptor() - { - if (reactor()) - reactor()->cancel_timer(this, 1); - } - -protected: - virtual int make_svc_handler(RealmSocket*& sh) - { - if (sh == nullptr) - ACE_NEW_RETURN(sh, RealmSocket, -1); - - sh->reactor(reactor()); - sh->set_session(new AuthSocket(*sh)); - return 0; - } - - virtual int handle_timeout(const ACE_Time_Value& /*current_time*/, const void* /*act = 0*/) - { - LOG_INFO("network", "Resuming acceptor"); - reactor()->cancel_timer(this, 1); - return reactor()->register_handler(this, ACE_Event_Handler::ACCEPT_MASK); - } - - virtual int handle_accept_error() - { -#if defined(ENFILE) && defined(EMFILE) - if (errno == ENFILE || errno == EMFILE) - { - LOG_ERROR("network", "Out of file descriptors, suspending incoming connections for 10 seconds"); - reactor()->remove_handler(this, ACE_Event_Handler::ACCEPT_MASK | ACE_Event_Handler::DONT_CALL); - reactor()->schedule_timer(this, nullptr, ACE_Time_Value(10)); - } -#endif - return 0; - } -}; - -#endif diff --git a/src/server/authserver/authserver.conf.dist b/src/server/authserver/authserver.conf.dist index c16aa95b97..b7d339ff9f 100644 --- a/src/server/authserver/authserver.conf.dist +++ b/src/server/authserver/authserver.conf.dist @@ -168,6 +168,23 @@ MySQLExecutable = "" IPLocationFile = "" # +# BanExpiryCheckInterval +# Description: Time (in seconds) between checks for expired bans +# Default: 60 +# + +BanExpiryCheckInterval = 60 + +# +# StrictVersionCheck +# Description: Prevent modified clients from connecting +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +StrictVersionCheck = 0 + +# ################################################################################################### ################################################################################################### diff --git a/src/server/database/Database/Callback.h b/src/server/database/Database/Callback.h deleted file mode 100644 index 32da3adc4b..0000000000 --- a/src/server/database/Database/Callback.h +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org> - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -#ifndef _CALLBACK_H -#define _CALLBACK_H - -#include <ace/Future.h> -#include <ace/Future_Set.h> -#include "QueryResult.h" - -typedef ACE_Future<QueryResult> QueryResultFuture; -typedef ACE_Future<PreparedQueryResult> PreparedQueryResultFuture; - -/*! A simple template using ACE_Future to manage callbacks from the thread and object that - issued the request. <ParamType> is variable type of parameter that is used as parameter - for the callback function. -*/ -#define CALLBACK_STAGE_INVALID uint8(-1) - -template <typename Result, typename ParamType, bool chain = false> -class QueryCallback -{ -public: - QueryCallback() : _param(), _stage(chain ? 0 : CALLBACK_STAGE_INVALID) {} - - //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery - void SetFutureResult(ACE_Future<Result> value) - { - _result = value; - } - - ACE_Future<Result> GetFutureResult() - { - return _result; - } - - int IsReady() - { - return _result.ready(); - } - - void GetResult(Result& res) - { - _result.get(res); - } - - void FreeResult() - { - _result.cancel(); - } - - void SetParam(ParamType value) - { - _param = value; - } - - ParamType GetParam() - { - return _param; - } - - //! Resets the stage of the callback chain - void ResetStage() - { - if (!chain) - return; - - _stage = 0; - } - - //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly - void NextStage() - { - if (!chain) - return; - - ++_stage; - } - - //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) - uint8 GetStage() - { - return _stage; - } - - //! Resets all underlying variables (param, result and stage) - void Reset() - { - SetParam(nullptr); - FreeResult(); - ResetStage(); - } - -private: - ACE_Future<Result> _result; - ParamType _param; - uint8 _stage; -}; - -template <typename Result, typename ParamType1, typename ParamType2, bool chain = false> -class QueryCallback_2 -{ -public: - QueryCallback_2() : _stage(chain ? 0 : CALLBACK_STAGE_INVALID) {} - - //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery - void SetFutureResult(ACE_Future<Result> value) - { - _result = value; - } - - ACE_Future<Result> GetFutureResult() - { - return _result; - } - - int IsReady() - { - return _result.ready(); - } - - void GetResult(Result& res) - { - _result.get(res); - } - - void FreeResult() - { - _result.cancel(); - } - - void SetFirstParam(ParamType1 value) - { - _param_1 = value; - } - - void SetSecondParam(ParamType2 value) - { - _param_2 = value; - } - - ParamType1 GetFirstParam() - { - return _param_1; - } - - ParamType2 GetSecondParam() - { - return _param_2; - } - - //! Resets the stage of the callback chain - void ResetStage() - { - if (!chain) - return; - - _stage = 0; - } - - //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly - void NextStage() - { - if (!chain) - return; - - ++_stage; - } - - //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) - uint8 GetStage() - { - return _stage; - } - - //! Resets all underlying variables (param, result and stage) - void Reset() - { - SetFirstParam(0); - SetSecondParam(nullptr); - FreeResult(); - ResetStage(); - } - -private: - ACE_Future<Result> _result; - ParamType1 _param_1; - ParamType2 _param_2; - uint8 _stage; -}; - -template <typename Result, typename ParamType1, typename ParamType2, typename ParamType3, bool chain = false> -class QueryCallback_3 -{ -public: - QueryCallback_3() : _stage(chain ? 0 : CALLBACK_STAGE_INVALID) {} - - //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery - void SetFutureResult(ACE_Future<Result> value) - { - _result = value; - } - - ACE_Future<Result> GetFutureResult() - { - return _result; - } - - int IsReady() - { - return _result.ready(); - } - - void GetResult(Result& res) - { - _result.get(res); - } - - void FreeResult() - { - _result.cancel(); - } - - void SetFirstParam(ParamType1 value) - { - _param_1 = value; - } - - void SetSecondParam(ParamType2 value) - { - _param_2 = value; - } - - void SetThirdParam(ParamType3 value) - { - _param_3 = value; - } - - ParamType1 GetFirstParam() - { - return _param_1; - } - - ParamType2 GetSecondParam() - { - return _param_2; - } - - ParamType3 GetThirdParam() - { - return _param_3; - } - - //! Resets the stage of the callback chain - void ResetStage() - { - if (!chain) - return; - - _stage = 0; - } - - //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly - void NextStage() - { - if (!chain) - return; - - ++_stage; - } - - //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) - uint8 GetStage() - { - return _stage; - } - - //! Resets all underlying variables (param, result and stage) - void Reset() - { - SetFirstParam(nullptr); - SetSecondParam(nullptr); - SetThirdParam(nullptr); - FreeResult(); - ResetStage(); - } - -private: - ACE_Future<Result> _result; - ParamType1 _param_1; - ParamType2 _param_2; - ParamType3 _param_3; - uint8 _stage; -}; - -#endif diff --git a/src/server/database/Database/DatabaseWorker.cpp b/src/server/database/Database/DatabaseWorker.cpp index e1d2926627..4e1bebb684 100644 --- a/src/server/database/Database/DatabaseWorker.cpp +++ b/src/server/database/Database/DatabaseWorker.cpp @@ -4,7 +4,7 @@ */ #include "DatabaseWorker.h" -#include "ProducerConsumerQueue.h" +#include "PCQueue.h" #include "SQLOperation.h" DatabaseWorker::DatabaseWorker(ProducerConsumerQueue<SQLOperation*>* newQueue, MySQLConnection* connection) diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp index 5c22bc26f2..08f2178f87 100644 --- a/src/server/database/Database/DatabaseWorkerPool.cpp +++ b/src/server/database/Database/DatabaseWorkerPool.cpp @@ -14,7 +14,7 @@ #include "MySQLPreparedStatement.h" #include "MySQLWorkaround.h" #include "PreparedStatement.h" -#include "ProducerConsumerQueue.h" +#include "PCQueue.h" #include "QueryCallback.h" #include "QueryHolder.h" #include "QueryResult.h" diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index c369aaa79b..62e8c09f71 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -20,7 +20,7 @@ void LoginDatabaseConnection::DoPrepareStatements() "LEFT JOIN account_access aa ON a.id = aa.id " "LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 " "LEFT JOIN ip_banned ipb ON ipb.ip = ? " - "WHERE a.username = ?", CONNECTION_SYNCH); + "WHERE a.username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE, "SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, " "ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, " @@ -30,14 +30,15 @@ void LoginDatabaseConnection::DoPrepareStatements() "LEFT JOIN account_access aa ON a.id = aa.id " "LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 " "LEFT JOIN ip_banned ipb ON ipb.ip = ? " - "WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_SYNCH); + "WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, " "aa.gmlevel, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) " "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? " - "AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_SYNCH); + "AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC); + PrepareStatement(LOGIN_SEL_IP_INFO, "SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE flag <> 3 ORDER BY name", CONNECTION_SYNCH); PrepareStatement(LOGIN_DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_SYNCH); + PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_IP_BANNED, "SELECT * FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity realmd', 'Failed login autoban')", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate", CONNECTION_SYNCH); @@ -55,6 +56,7 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_NUM_CHARS_ON_REALM, "SELECT numchars FROM realmcharacters WHERE realmid = ? AND acctid= ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 312440d6fd..9ea01c0b87 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -19,6 +19,7 @@ enum LoginDatabaseStatements : uint32 LOGIN_SEL_REALMLIST, LOGIN_DEL_EXPIRED_IP_BANS, LOGIN_UPD_EXPIRED_ACCOUNT_BANS, + LOGIN_SEL_IP_INFO, LOGIN_SEL_IP_BANNED, LOGIN_INS_IP_AUTO_BANNED, LOGIN_SEL_ACCOUNT_BANNED, @@ -37,6 +38,7 @@ enum LoginDatabaseStatements : uint32 LOGIN_SEL_ACCOUNT_INFO_BY_NAME, LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, LOGIN_SEL_NUM_CHARS_ON_REALM, + LOGIN_SEL_REALM_CHARACTER_COUNTS, LOGIN_SEL_ACCOUNT_BY_IP, LOGIN_INS_IP_BANNED, LOGIN_DEL_IP_NOT_BANNED, diff --git a/src/server/game/ArenaSpectator/ArenaSpectator.cpp b/src/server/game/ArenaSpectator/ArenaSpectator.cpp index 04fcf7b77b..dd8bf4e004 100644 --- a/src/server/game/ArenaSpectator/ArenaSpectator.cpp +++ b/src/server/game/ArenaSpectator/ArenaSpectator.cpp @@ -4,188 +4,319 @@ #include "ArenaSpectator.h" #include "BattlegroundMgr.h" -#include "Chat.h" #include "LFGMgr.h" -#include "ObjectMgr.h" -#include "Opcodes.h" +#include "Map.h" +#include "Pet.h" #include "Player.h" -#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "SpellAuras.h" #include "World.h" -namespace ArenaSpectator +bool ArenaSpectator::HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args) { - bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args) + Player* player = handler->GetSession()->GetPlayer(); + std::list<std::string> errors; + + if (!*args) { - Player* player = handler->GetSession()->GetPlayer(); - std::list<std::string> errors; - if (!*args) - { - handler->SendSysMessage("Missing player name."); - return true; - } - if (player->IsSpectator()) - { - if (player->FindMap() && player->FindMap()->IsBattleArena()) - { - HandleSpectatorWatchCommand(handler, args); - return true; - } - handler->PSendSysMessage("You are already spectacting arena."); - return true; - } - if (player->getClass() == CLASS_DEATH_KNIGHT && player->GetMapId() == 609) - { - handler->PSendSysMessage("Death Knights can't spectate before finishing questline."); - return true; - } + handler->SendSysMessage("Missing player name."); + return true; + } - std::string name = std::string(args); - Player* spectate = ObjectAccessor::FindPlayerByName(name); - if (!spectate) - { - handler->SendSysMessage("Requested player not found."); - return true; - } - if (spectate->IsSpectator()) - { - handler->SendSysMessage("Requested player is a spectator."); - return true; - } - if (!spectate->FindMap() || !spectate->FindMap()->IsBattleArena()) - { - handler->SendSysMessage("Requested player is not in arena."); - return true; - } - BattlegroundMap* bgmap = ((BattlegroundMap*)spectate->FindMap()); - if (!bgmap->GetBG() || bgmap->GetBG()->GetStatus() == STATUS_WAIT_LEAVE) + if (player->IsSpectator()) + { + if (player->FindMap() && player->FindMap()->IsBattleArena()) { - handler->SendSysMessage("This arena battle has finished."); + HandleSpectatorWatchCommand(handler, args); return true; } + handler->PSendSysMessage("You are already spectacting arena."); + return true; + } + + if (player->getClass() == CLASS_DEATH_KNIGHT && player->GetMapId() == 609) + { + handler->PSendSysMessage("Death Knights can't spectate before finishing questline."); + return true; + } + + std::string name = std::string(args); + Player* spectate = ObjectAccessor::FindPlayerByName(name); + if (!spectate) + { + handler->SendSysMessage("Requested player not found."); + return true; + } + + if (spectate->IsSpectator()) + { + handler->SendSysMessage("Requested player is a spectator."); + return true; + } + + if (!spectate->FindMap() || !spectate->FindMap()->IsBattleArena()) + { + handler->SendSysMessage("Requested player is not in arena."); + return true; + } + + BattlegroundMap* bgmap = ((BattlegroundMap*)spectate->FindMap()); + if (!bgmap->GetBG() || bgmap->GetBG()->GetStatus() == STATUS_WAIT_LEAVE) + { + handler->SendSysMessage("This arena battle has finished."); + return true; + } + + if (player->IsBeingTeleported() || !player->IsInWorld()) + errors.push_back("Can't use while being teleported."); + + if (!player->FindMap() || player->FindMap()->Instanceable()) + errors.push_back("Can't use while in instance, bg or arena."); + + if (player->GetVehicle()) + errors.push_back("Can't be on a vehicle."); - if (player->IsBeingTeleported() || !player->IsInWorld()) - errors.push_back("Can't use while being teleported."); - if (!player->FindMap() || player->FindMap()->Instanceable()) - errors.push_back("Can't use while in instance, bg or arena."); - if (player->GetVehicle()) - errors.push_back("Can't be on a vehicle."); - if (player->IsInCombat()) - errors.push_back("Can't be in combat."); - if (player->isUsingLfg()) - errors.push_back("Can't spectate while using LFG system."); - if (player->InBattlegroundQueue()) - errors.push_back("Can't be queued for arena or bg."); - if (player->GetGroup()) - errors.push_back("Can't be in a group."); - if (player->HasUnitState(UNIT_STATE_ISOLATED)) - errors.push_back("Can't be isolated."); - if (player->m_mover != player) - errors.push_back("You must control yourself."); - if (player->IsInFlight()) - errors.push_back("Can't be in flight."); - if (player->IsMounted()) - errors.push_back("Dismount before spectating."); - if (!player->IsAlive()) - errors.push_back("Must be alive."); - if (!player->m_Controlled.empty()) - errors.push_back("Can't be controlling creatures."); - - const Unit::VisibleAuraMap* va = player->GetVisibleAuras(); - for (Unit::VisibleAuraMap::const_iterator itr = va->begin(); itr != va->end(); ++itr) - if (Aura* aura = itr->second->GetBase()) - if (!itr->second->IsPositive() && !aura->IsPermanent() && aura->GetDuration() < HOUR * IN_MILLISECONDS) + if (player->IsInCombat()) + errors.push_back("Can't be in combat."); + + if (player->isUsingLfg()) + errors.push_back("Can't spectate while using LFG system."); + + if (player->InBattlegroundQueue()) + errors.push_back("Can't be queued for arena or bg."); + + if (player->GetGroup()) + errors.push_back("Can't be in a group."); + + if (player->HasUnitState(UNIT_STATE_ISOLATED)) + errors.push_back("Can't be isolated."); + + if (player->m_mover != player) + errors.push_back("You must control yourself."); + + if (player->IsInFlight()) + errors.push_back("Can't be in flight."); + + if (player->IsMounted()) + errors.push_back("Dismount before spectating."); + + if (!player->IsAlive()) + errors.push_back("Must be alive."); + + if (!player->m_Controlled.empty()) + errors.push_back("Can't be controlling creatures."); + + const Unit::VisibleAuraMap* va = player->GetVisibleAuras(); + for (auto itr = va->begin(); itr != va->end(); ++itr) + if (Aura* aura = itr->second->GetBase()) + if (!itr->second->IsPositive() && !aura->IsPermanent() && aura->GetDuration() < HOUR * IN_MILLISECONDS) + { + switch (aura->GetSpellInfo()->Id) { - switch (aura->GetSpellInfo()->Id) - { - case lfg::LFG_SPELL_DUNGEON_DESERTER: - case lfg::LFG_SPELL_DUNGEON_COOLDOWN: - case 26013: // bg deserter - case 57724: // sated - case 57723: // exhaustion - case 25771: // forbearance - case 15007: // resurrection sickness - case 24755: // Tricked or Treated (z eventu) - continue; - } - - errors.push_back("Can't have negative auras."); - break; + case lfg::LFG_SPELL_DUNGEON_DESERTER: + case lfg::LFG_SPELL_DUNGEON_COOLDOWN: + case 26013: // bg deserter + case 57724: // sated + case 57723: // exhaustion + case 25771: // forbearance + case 15007: // resurrection sickness + case 24755: // Tricked or Treated (z eventu) + continue; } - if (uint32 inviteInstanceId = player->GetPendingSpectatorInviteInstanceId()) - { - if (Battleground* tbg = sBattlegroundMgr->GetBattleground(inviteInstanceId)) - tbg->RemoveToBeTeleported(player->GetGUID()); - player->SetPendingSpectatorInviteInstanceId(0); - } + errors.push_back("Can't have negative auras."); + break; + } - bool bgPreparation = false; - if ((!handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS) || + if (uint32 inviteInstanceId = player->GetPendingSpectatorInviteInstanceId()) + { + if (Battleground* tbg = sBattlegroundMgr->GetBattleground(inviteInstanceId)) + tbg->RemoveToBeTeleported(player->GetGUID()); + player->SetPendingSpectatorInviteInstanceId(0); + } + + bool bgPreparation = false; + if ((!handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS) || (handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_WAIT_JOIN && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS)) - { - bgPreparation = true; - handler->SendSysMessage("Arena is not in progress yet. You will be invited as soon as it starts."); - bgmap->GetBG()->AddToBeTeleported(player->GetGUID(), spectate->GetGUID()); - player->SetPendingSpectatorInviteInstanceId(spectate->GetBattlegroundId()); - } + { + bgPreparation = true; + handler->SendSysMessage("Arena is not in progress yet. You will be invited as soon as it starts."); + bgmap->GetBG()->AddToBeTeleported(player->GetGUID(), spectate->GetGUID()); + player->SetPendingSpectatorInviteInstanceId(spectate->GetBattlegroundId()); + } - if (!errors.empty()) + if (!errors.empty()) + { + handler->PSendSysMessage("To spectate, please fix the following:"); + for (std::list<std::string>::const_iterator itr = errors.begin(); itr != errors.end(); ++itr) + handler->PSendSysMessage("- %s", (*itr).c_str()); + + return true; + } + + if (bgPreparation) + return true; + + float z = spectate->GetMapId() == 618 ? std::max(28.27f, spectate->GetPositionZ() + 0.25f) : spectate->GetPositionZ() + 0.25f; + + player->SetPendingSpectatorForBG(spectate->GetBattlegroundId()); + player->SetBattlegroundId(spectate->GetBattlegroundId(), spectate->GetBattlegroundTypeId(), PLAYER_MAX_BATTLEGROUND_QUEUES, false, false, TEAM_NEUTRAL); + player->SetEntryPoint(); + player->TeleportTo(spectate->GetMapId(), spectate->GetPositionX(), spectate->GetPositionY(), z, spectate->GetOrientation(), TELE_TO_GM_MODE); + + return true; +} + +bool ArenaSpectator::HandleSpectatorWatchCommand(ChatHandler* handler, char const* args) +{ + if (!*args) + return true; + + Player* player = handler->GetSession()->GetPlayer(); + if (!player->IsSpectator()) + return true; + + if (!player->FindMap() || !player->FindMap()->IsBattleArena()) + return true; + + Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG(); + if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS) + return true; + + std::string name = std::string(args); + Player* spectate = ObjectAccessor::FindPlayerByName(name); + if (!spectate || !spectate->IsAlive() || spectate->IsSpectator() || spectate->GetGUID() == player->GetGUID() || !spectate->IsInWorld() || !spectate->FindMap() || spectate->IsBeingTeleported() || spectate->FindMap() != player->FindMap() || !bg->IsPlayerInBattleground(spectate->GetGUID())) + return true; + + if (WorldObject* o = player->GetViewpoint()) + if (Unit* u = o->ToUnit()) { - handler->PSendSysMessage("To spectate, please fix the following:"); - for (std::list<std::string>::const_iterator itr = errors.begin(); itr != errors.end(); ++itr) - handler->PSendSysMessage("- %s", (*itr).c_str()); + u->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, player->GetGUID()); + player->RemoveAurasDueToSpell(SPECTATOR_SPELL_BINDSIGHT, player->GetGUID(), (1 << EFFECT_1)); - return true; + if (u->GetGUID() == spectate->GetGUID()) + return true; } - if (bgPreparation) - return true; - - player->SetPendingSpectatorForBG(spectate->GetBattlegroundId()); - player->SetBattlegroundId(spectate->GetBattlegroundId(), spectate->GetBattlegroundTypeId(), PLAYER_MAX_BATTLEGROUND_QUEUES, false, false, TEAM_NEUTRAL); - player->SetEntryPoint(); - float z = spectate->GetMapId() == 618 ? std::max(28.27f, spectate->GetPositionZ() + 0.25f) : spectate->GetPositionZ() + 0.25f; - player->TeleportTo(spectate->GetMapId(), spectate->GetPositionX(), spectate->GetPositionY(), z, spectate->GetOrientation(), TELE_TO_GM_MODE); + if (player->GetUInt64Value(PLAYER_FARSIGHT) || player->m_seer != player) // pussywizard: below this point we must not have a viewpoint! return true; - } - bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args) - { - if (!*args) - return true; + if (player->HaveAtClient(spectate)) + player->CastSpell(spectate, SPECTATOR_SPELL_BINDSIGHT, true); - Player* player = handler->GetSession()->GetPlayer(); - if (!player->IsSpectator()) - return true; + return true; +} - if (!player->FindMap() || !player->FindMap()->IsBattleArena()) - return true; +void ArenaSpectator::CreatePacket(WorldPacket& data, std::string const& message) +{ + size_t len = message.length(); + data.Initialize(SMSG_MESSAGECHAT, 1 + 4 + 8 + 4 + 8 + 4 + 1 + len + 1); + data << uint8(CHAT_MSG_WHISPER); + data << uint32(LANG_ADDON); + data << uint64(0); + data << uint32(0); + data << uint64(0); + data << uint32(len + 1); + data << message; + data << uint8(0); +} - Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG(); - if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS) - return true; +void ArenaSpectator::HandleResetCommand(Player* player) +{ + if (!player->FindMap() || !player->IsInWorld() || !player->FindMap()->IsBattleArena()) + return; - std::string name = std::string(args); - Player* spectate = ObjectAccessor::FindPlayerByName(name); - if (!spectate || !spectate->IsAlive() || spectate->IsSpectator() || spectate->GetGUID() == player->GetGUID() || !spectate->IsInWorld() || !spectate->FindMap() || spectate->IsBeingTeleported() || spectate->FindMap() != player->FindMap() || !bg->IsPlayerInBattleground(spectate->GetGUID())) - return true; + Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG(); + if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS) + return; - if (WorldObject* o = player->GetViewpoint()) - if (Unit* u = o->ToUnit()) - { - u->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, player->GetGUID()); - player->RemoveAurasDueToSpell(SPECTATOR_SPELL_BINDSIGHT, player->GetGUID(), (1 << EFFECT_1)); + Battleground::BattlegroundPlayerMap const& pl = bg->GetPlayers(); + for (Battleground::BattlegroundPlayerMap::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) + { + if (player->HasReceivedSpectatorResetFor(itr->first)) + continue; - if (u->GetGUID() == spectate->GetGUID()) - return true; - } + Player* plr = itr->second; + player->AddReceivedSpectatorResetFor(itr->first); - if (player->GetGuidValue(PLAYER_FARSIGHT) || player->m_seer != player) // pussywizard: below this point we must not have a viewpoint! - return true; + SendCommand_String(player, itr->first, "NME", plr->GetName().c_str()); + // Xinef: addon compatibility + SendCommand_UInt32Value(player, itr->first, "TEM", plr->GetBgTeamId() == TEAM_ALLIANCE ? ALLIANCE : HORDE); + SendCommand_UInt32Value(player, itr->first, "CLA", plr->getClass()); + SendCommand_UInt32Value(player, itr->first, "MHP", plr->GetMaxHealth()); + SendCommand_UInt32Value(player, itr->first, "CHP", plr->GetHealth()); + SendCommand_UInt32Value(player, itr->first, "STA", plr->IsAlive() ? 1 : 0); + Powers ptype = plr->getPowerType(); + SendCommand_UInt32Value(player, itr->first, "PWT", ptype); + SendCommand_UInt32Value(player, itr->first, "MPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetMaxPower(ptype) / 10 : plr->GetMaxPower(ptype)); + SendCommand_UInt32Value(player, itr->first, "CPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetPower(ptype) / 10 : plr->GetPower(ptype)); + Pet* pet = plr->GetPet(); + SendCommand_UInt32Value(player, itr->first, "PHP", pet && pet->GetCreatureTemplate()->family ? (uint32)pet->GetHealthPct() : 0); + SendCommand_UInt32Value(player, itr->first, "PET", pet ? pet->GetCreatureTemplate()->family : 0); + SendCommand_GUID(player, itr->first, "TRG", plr->GetTarget()); + SendCommand_UInt32Value(player, itr->first, "RES", 1); + SendCommand_UInt32Value(player, itr->first, "CDC", 1); + SendCommand_UInt32Value(player, itr->first, "TIM", (bg->GetStartTime() < 46 * MINUTE * IN_MILLISECONDS) ? (46 * MINUTE * IN_MILLISECONDS - bg->GetStartTime()) / IN_MILLISECONDS : 0); + // "SPE" not here (only possible to send starting a new cast) + + // send all "CD" + SpellCooldowns const& sc = plr->GetSpellCooldownMap(); + for (SpellCooldowns::const_iterator itrc = sc.begin(); itrc != sc.end(); ++itrc) + if (itrc->second.sendToSpectator && itrc->second.maxduration >= SPECTATOR_COOLDOWN_MIN * IN_MILLISECONDS && itrc->second.maxduration <= SPECTATOR_COOLDOWN_MAX * IN_MILLISECONDS) + if (uint32 cd = (getMSTimeDiff(getMSTime(), itrc->second.end) / 1000)) + SendCommand_Cooldown(player, itr->first, "ACD", itrc->first, cd, itrc->second.maxduration / 1000); + + // send all visible "AUR" + Unit::VisibleAuraMap const* visibleAuras = plr->GetVisibleAuras(); + for (Unit::VisibleAuraMap::const_iterator aitr = visibleAuras->begin(); aitr != visibleAuras->end(); ++aitr) + { + Aura* aura = aitr->second->GetBase(); + if (ShouldSendAura(aura, aitr->second->GetEffectMask(), plr->GetGUID(), false)) + SendCommand_Aura(player, itr->first, "AUR", aura->GetCasterGUID(), aura->GetSpellInfo()->Id, aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel, aura->GetDuration(), aura->GetMaxDuration(), (aura->GetCharges() > 1 ? aura->GetCharges() : aura->GetStackAmount()), false); + } + } +} - if (player->HaveAtClient(spectate)) - player->CastSpell(spectate, SPECTATOR_SPELL_BINDSIGHT, true); +bool ArenaSpectator::ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove) +{ + if (aura->GetSpellInfo()->SpellIconID == 1 || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_NO_AURA_ICON)) + return false; + if (remove || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_DONT_BREAK_STEALTH) || aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_GENERIC) return true; + + for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) + { + if (effMask & (1 << i)) + { + AuraType at = aura->GetEffect(i)->GetAuraType(); + if ((aura->GetEffect(i)->GetAmount() && (aura->GetSpellInfo()->IsPositive() || targetGUID != aura->GetCasterGUID())) || + at == SPELL_AURA_MECHANIC_IMMUNITY || at == SPELL_AURA_EFFECT_IMMUNITY || at == SPELL_AURA_STATE_IMMUNITY || at == SPELL_AURA_SCHOOL_IMMUNITY || at == SPELL_AURA_DISPEL_IMMUNITY) + return true; + } } + return false; +} + +template<> +AC_GAME_API void ArenaSpectator::SendPacketTo(const Player* player, std::string&& message) +{ + WorldPacket data; + CreatePacket(data, message); + player->GetSession()->SendPacket(&data); } + +template<> +AC_GAME_API void ArenaSpectator::SendPacketTo(const Map* map, std::string&& message) +{ + if (!map->IsBattleArena()) + return; + + Battleground* bg = ((BattlegroundMap*)map)->GetBG(); + if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS) + return; + + WorldPacket data; + CreatePacket(data, message); + bg->SpectatorsSendPacket(data); +}
\ No newline at end of file diff --git a/src/server/game/ArenaSpectator/ArenaSpectator.h b/src/server/game/ArenaSpectator/ArenaSpectator.h index 433e7af63a..f87adc54fa 100644 --- a/src/server/game/ArenaSpectator/ArenaSpectator.h +++ b/src/server/game/ArenaSpectator/ArenaSpectator.h @@ -5,15 +5,17 @@ #ifndef AZEROTHCORE_ARENASPECTATOR_H #define AZEROTHCORE_ARENASPECTATOR_H -#include "Battleground.h" #include "Chat.h" -#include "LFGMgr.h" -#include "Map.h" -#include "Pet.h" -#include "Player.h" -#include "SpellAuraEffects.h" -#include "SpellAuras.h" -#include "World.h" +#include "Common.h" +#include "ObjectDefines.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "StringFormat.h" + +class Aura; +class Player; +class Map; +class WorldPacket; #define SPECTATOR_ADDON_VERSION 27 #define SPECTATOR_BUFFER_LEN 150 @@ -25,196 +27,78 @@ namespace ArenaSpectator { - template<class T> inline void SendCommand(T* o, const char* format, ...) ATTR_PRINTF(2, 3); - inline void CreatePacket(WorldPacket& data, const char* m); - inline void SendPacketTo(const Player* p, const char* m); - inline void SendPacketTo(const Map* map, const char* m); - inline void HandleResetCommand(Player* p); - inline bool ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove); - - template<class T> inline void SendCommand_String(T* p, ObjectGuid targetGUID, const char* prefix, const std::string& c); - template<class T> inline void SendCommand_UInt32Value(T* o, ObjectGuid targetGUID, const char* prefix, uint32 t); - template<class T> inline void SendCommand_GUID(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid t); - template<class T> inline void SendCommand_Spell(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, int32 casttime); - template<class T> inline void SendCommand_Cooldown(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur); - template<class T> inline void SendCommand_Aura(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove); - - bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args); - bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args); - - // definitions below: - template<class T> - void SendCommand(T* o, const char* format, ...) - { - if (!format) - return; - char buffer[SPECTATOR_BUFFER_LEN]; - va_list ap; - va_start(ap, format); - vsnprintf(buffer, SPECTATOR_BUFFER_LEN, format, ap); - va_end(ap); - SendPacketTo(o, buffer); - } - - void CreatePacket(WorldPacket& data, const char* m) - { - size_t len = strlen(m); - data.Initialize(SMSG_MESSAGECHAT, 1 + 4 + 8 + 4 + 8 + 4 + 1 + len + 1); - data << uint8(CHAT_MSG_WHISPER); - data << uint32(LANG_ADDON); - data << uint64(0); - data << uint32(0); - data << uint64(0); - data << uint32(len + 1); - data << m; - data << uint8(0); - } - - void SendPacketTo(const Player* p, const char* m) - { - WorldPacket data; - CreatePacket(data, m); - p->GetSession()->SendPacket(&data); - } + AC_GAME_API void SendPacketTo(const T* object, std::string&& message); - void SendPacketTo(const Map* map, const char* m) + template<class T, typename Format, typename... Args> + inline void SendCommand(T* o, Format&& fmt, Args&& ... args) { - if (!map->IsBattleArena()) - return; - Battleground* bg = ((BattlegroundMap*)map)->GetBG(); - if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS) - return; - WorldPacket data; - CreatePacket(data, m); - bg->SpectatorsSendPacket(data); + SendPacketTo(o, Acore::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...)); } template<class T> - void SendCommand_String(T* o, ObjectGuid targetGUID, const char* prefix, const char* c) + inline void SendCommand_String(T* o, ObjectGuid targetGUID, const char* prefix, const char* c) { if (!targetGUID.IsPlayer()) return; - SendCommand(o, "%s0x%016llX;%s=%s;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, c); + + SendCommand(o, "%s0x%016llX;%s=%s;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, c); } template<class T> - void SendCommand_UInt32Value(T* o, ObjectGuid targetGUID, const char* prefix, uint32 t) + inline void SendCommand_UInt32Value(T* o, ObjectGuid targetGUID, const char* prefix, uint32 t) { if (!targetGUID.IsPlayer()) return; - SendCommand(o, "%s0x%016llX;%s=%u;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, t); + + SendCommand(o, "%s0x%016llX;%s=%u;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, t); } template<class T> - void SendCommand_GUID(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid t) + inline void SendCommand_GUID(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid t) { if (!targetGUID.IsPlayer()) return; - SendCommand(o, "%s0x%016llX;%s=0x%016llX;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, (unsigned long long)t.GetRawValue()); + + SendCommand(o, "%s0x%016llX;%s=0x%016llX;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, t.GetRawValue()); } template<class T> - void SendCommand_Spell(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, int32 casttime) + inline void SendCommand_Spell(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, int32 casttime) { if (!targetGUID.IsPlayer()) return; - SendCommand(o, "%s0x%016llX;%s=%u,%i;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, id, casttime); + + SendCommand(o, "%s0x%016llX;%s=%u,%i;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, id, casttime); } template<class T> - void SendCommand_Cooldown(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur) + inline void SendCommand_Cooldown(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur) { if (!targetGUID.IsPlayer()) return; + if (const SpellInfo* si = sSpellMgr->GetSpellInfo(id)) if (si->SpellIconID == 1) return; - SendCommand(o, "%s0x%016llX;%s=%u,%u,%u;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, id, dur, maxdur); + + SendCommand(o, "%s0x%016llX;%s=%u,%u,%u;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, id, dur, maxdur); } template<class T> - void SendCommand_Aura(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove) + inline void SendCommand_Aura(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove) { if (!targetGUID.IsPlayer()) return; - SendCommand(o, "%s0x%016llX;%s=%u,%u,%i,%i,%u,%u,%u,0x%016llX;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, remove ? 1 : 0, stack, dur, maxdur, id, dispel, isDebuff ? 1 : 0, (unsigned long long)caster.GetRawValue()); - } - void HandleResetCommand(Player* p) - { - if (!p->FindMap() || !p->IsInWorld() || !p->FindMap()->IsBattleArena()) - return; - Battleground* bg = ((BattlegroundMap*)p->FindMap())->GetBG(); - if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS) - return; - Battleground::BattlegroundPlayerMap const& pl = bg->GetPlayers(); - for (Battleground::BattlegroundPlayerMap::const_iterator itr = pl.begin(); itr != pl.end(); ++itr) - { - if (p->HasReceivedSpectatorResetFor(itr->first)) - continue; - - Player* plr = itr->second; - p->AddReceivedSpectatorResetFor(itr->first); - - SendCommand_String(p, itr->first, "NME", plr->GetName().c_str()); - // Xinef: addon compatibility - SendCommand_UInt32Value(p, itr->first, "TEM", plr->GetBgTeamId() == TEAM_ALLIANCE ? ALLIANCE : HORDE); - SendCommand_UInt32Value(p, itr->first, "CLA", plr->getClass()); - SendCommand_UInt32Value(p, itr->first, "MHP", plr->GetMaxHealth()); - SendCommand_UInt32Value(p, itr->first, "CHP", plr->GetHealth()); - SendCommand_UInt32Value(p, itr->first, "STA", plr->IsAlive() ? 1 : 0); - Powers ptype = plr->getPowerType(); - SendCommand_UInt32Value(p, itr->first, "PWT", ptype); - SendCommand_UInt32Value(p, itr->first, "MPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetMaxPower(ptype) / 10 : plr->GetMaxPower(ptype)); - SendCommand_UInt32Value(p, itr->first, "CPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetPower(ptype) / 10 : plr->GetPower(ptype)); - Pet* pet = plr->GetPet(); - SendCommand_UInt32Value(p, itr->first, "PHP", pet && pet->GetCreatureTemplate()->family ? (uint32)pet->GetHealthPct() : 0); - SendCommand_UInt32Value(p, itr->first, "PET", pet ? pet->GetCreatureTemplate()->family : 0); - SendCommand_GUID(p, itr->first, "TRG", plr->GetTarget()); - SendCommand_UInt32Value(p, itr->first, "RES", 1); - SendCommand_UInt32Value(p, itr->first, "CDC", 1); - SendCommand_UInt32Value(p, itr->first, "TIM", (bg->GetStartTime() < 46 * MINUTE * IN_MILLISECONDS) ? (46 * MINUTE * IN_MILLISECONDS - bg->GetStartTime()) / IN_MILLISECONDS : 0); - // "SPE" not here (only possible to send starting a new cast) - - // send all "CD" - SpellCooldowns const& sc = plr->GetSpellCooldownMap(); - for (SpellCooldowns::const_iterator itrc = sc.begin(); itrc != sc.end(); ++itrc) - if (itrc->second.sendToSpectator && itrc->second.maxduration >= SPECTATOR_COOLDOWN_MIN * IN_MILLISECONDS && itrc->second.maxduration <= SPECTATOR_COOLDOWN_MAX * IN_MILLISECONDS) - if (uint32 cd = (getMSTimeDiff(World::GetGameTimeMS(), itrc->second.end) / 1000)) - SendCommand_Cooldown(p, itr->first, "ACD", itrc->first, cd, itrc->second.maxduration / 1000); - - // send all visible "AUR" - Unit::VisibleAuraMap const* visibleAuras = plr->GetVisibleAuras(); - for (Unit::VisibleAuraMap::const_iterator aitr = visibleAuras->begin(); aitr != visibleAuras->end(); ++aitr) - { - Aura* aura = aitr->second->GetBase(); - if (ShouldSendAura(aura, aitr->second->GetEffectMask(), plr->GetGUID(), false)) - SendCommand_Aura(p, itr->first, "AUR", aura->GetCasterGUID(), aura->GetSpellInfo()->Id, aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel, aura->GetDuration(), aura->GetMaxDuration(), (aura->GetCharges() > 1 ? aura->GetCharges() : aura->GetStackAmount()), false); - } - } + SendCommand(o, "%s0x%016llX;%s=%u,%u,%i,%i,%u,%u,%u,0x%016llX;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, remove ? 1 : 0, stack, dur, maxdur, id, dispel, isDebuff ? 1 : 0, caster.GetRawValue()); } - bool ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove) - { - if (aura->GetSpellInfo()->SpellIconID == 1 || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_NO_AURA_ICON)) - return false; - - if (remove || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_AURA_CC) || aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_GENERIC) - return true; - - for(uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) - { - if (effMask & (1 << i)) - { - AuraType at = aura->GetEffect(i)->GetAuraType(); - if ((aura->GetEffect(i)->GetAmount() && (aura->GetSpellInfo()->IsPositive() || targetGUID != aura->GetCasterGUID())) || - at == SPELL_AURA_MECHANIC_IMMUNITY || at == SPELL_AURA_EFFECT_IMMUNITY || at == SPELL_AURA_STATE_IMMUNITY || at == SPELL_AURA_SCHOOL_IMMUNITY || at == SPELL_AURA_DISPEL_IMMUNITY) - return true; - } - } - return false; - } + AC_GAME_API bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args); + AC_GAME_API bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args); + AC_GAME_API void CreatePacket(WorldPacket& data, std::string const& message); + AC_GAME_API void HandleResetCommand(Player* player); + AC_GAME_API bool ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove); } #endif diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index fb5b4dbbfc..047f65034d 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -4,6 +4,7 @@ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ +#include "Pet.h" #include "ArenaSpectator.h" #include "Battleground.h" #include "Common.h" @@ -13,7 +14,7 @@ #include "InstanceScript.h" #include "Log.h" #include "ObjectMgr.h" -#include "Pet.h" +#include "Player.h" #include "ScriptMgr.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d50b8fbfb1..2d33da0414 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -58,7 +58,6 @@ #include "QuestDef.h" #include "QueryHolder.h" #include "ReputationMgr.h" -#include "revision.h" #include "Realm.h" #include "SavingSystem.h" #include "ScriptMgr.h" diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp index be3f753b35..cf1cf3664a 100644 --- a/src/server/game/Entities/Player/PlayerQuest.cpp +++ b/src/server/game/Entities/Player/PlayerQuest.cpp @@ -12,7 +12,7 @@ #include "Player.h" #include "PoolMgr.h" #include "ReputationMgr.h" -#include "revision.h" +#include "GitRevision.h" #include "ScriptMgr.h" #include "SpellAuraEffects.h" @@ -559,8 +559,8 @@ void Player::AddQuest(Quest const* quest, Object* questGiver) auto stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_QUEST_TRACK); stmt->setUInt32(0, quest_id); stmt->setUInt32(1, GetGUID().GetCounter()); - stmt->setString(2, _HASH); - stmt->setString(3, _DATE); + stmt->setString(2, GitRevision::GetHash()); + stmt->setString(3, GitRevision::GetDate()); // add to Quest Tracker CharacterDatabase.Execute(stmt); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index bc44bb30be..bb7c61bfab 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -3884,6 +3884,7 @@ void Unit::HandleSafeUnitPointersOnDelete(Unit* thisUnit) return; for (std::set<SafeUnitPointer*>::iterator itr = thisUnit->SafeUnitPointerSet.begin(); itr != thisUnit->SafeUnitPointerSet.end(); ++itr) (*itr)->UnitDeleted(); + thisUnit->SafeUnitPointerSet.clear(); } diff --git a/src/server/game/PrecompiledHeaders/gamePCH.h b/src/server/game/PrecompiledHeaders/gamePCH.h index d3eda38b6c..047a26b264 100644 --- a/src/server/game/PrecompiledHeaders/gamePCH.h +++ b/src/server/game/PrecompiledHeaders/gamePCH.h @@ -4,8 +4,6 @@ //add here most rarely modified headers to speed up debug build compilation -#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it - #include "Common.h" #include "Log.h" #include "MapManager.h" @@ -14,3 +12,4 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "SharedDefines.h" +#include "WorldSocket.h" diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index b2bed632da..c46982de3d 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -345,18 +345,18 @@ void ScriptMgr::OnNetworkStop() FOREACH_SCRIPT(ServerScript)->OnNetworkStop(); } -void ScriptMgr::OnSocketOpen(WorldSocket* socket) +void ScriptMgr::OnSocketOpen(std::shared_ptr<WorldSocket> socket) { ASSERT(socket); FOREACH_SCRIPT(ServerScript)->OnSocketOpen(socket); } -void ScriptMgr::OnSocketClose(WorldSocket* socket, bool wasNew) +void ScriptMgr::OnSocketClose(std::shared_ptr<WorldSocket> socket) { ASSERT(socket); - FOREACH_SCRIPT(ServerScript)->OnSocketClose(socket, wasNew); + FOREACH_SCRIPT(ServerScript)->OnSocketClose(socket); } void ScriptMgr::OnPacketReceive(WorldSession* session, WorldPacket const& packet) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index a744228e44..fc1033f8ae 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -144,11 +144,11 @@ public: virtual void OnNetworkStop() { } // Called when a remote socket establishes a connection to the server. Do not store the socket object. - virtual void OnSocketOpen(WorldSocket* /*socket*/) { } + virtual void OnSocketOpen(std::shared_ptr<WorldSocket> /*socket*/) { } // Called when a socket is closed. Do not store the socket object, and do not rely on the connection // being open; it is not. - virtual void OnSocketClose(WorldSocket* /*socket*/, bool /*wasNew*/) { } + virtual void OnSocketClose(std::shared_ptr<WorldSocket> /*socket*/) { } // Called when a packet is sent to a client. The packet object is a copy of the original packet, so reading // and modifying it is safe. @@ -1456,8 +1456,8 @@ public: /* SpellScriptLoader */ public: /* ServerScript */ void OnNetworkStart(); void OnNetworkStop(); - void OnSocketOpen(WorldSocket* socket); - void OnSocketClose(WorldSocket* socket, bool wasNew); + void OnSocketOpen(std::shared_ptr<WorldSocket> socket); + void OnSocketClose(std::shared_ptr<WorldSocket> socket); void OnPacketReceive(WorldSession* session, WorldPacket const& packet); void OnPacketSend(WorldSession* session, WorldPacket const& packet); diff --git a/src/server/game/Server/Packets/PacketUtilities.cpp b/src/server/game/Server/Packets/PacketUtilities.cpp index e73ab73806..d11b96a44f 100644 --- a/src/server/game/Server/Packets/PacketUtilities.cpp +++ b/src/server/game/Server/Packets/PacketUtilities.cpp @@ -31,7 +31,7 @@ bool WorldPackets::Strings::Utf8::Validate(std::string const& value) //bool WorldPackets::Strings::Hyperlinks::Validate(std::string const& value) //{ -// if (!Warhead::Hyperlinks::CheckAllLinks(value)) +// if (!Acore::Hyperlinks::CheckAllLinks(value)) // throw InvalidHyperlinkException(value); // // return true; diff --git a/src/server/game/Server/Protocol/PacketLog.cpp b/src/server/game/Server/Protocol/PacketLog.cpp index 7af5fa09e5..073b314278 100644 --- a/src/server/game/Server/Protocol/PacketLog.cpp +++ b/src/server/game/Server/Protocol/PacketLog.cpp @@ -1,17 +1,18 @@ /* * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2008-2021 TrinityCore <http://www.trinitycore.org/> * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ #include "PacketLog.h" #include "Config.h" +#include "IpAddress.h" #include "Timer.h" #include "WorldPacket.h" #pragma pack(push, 1) - // Packet logging structures in PKT 3.1 format +// Packet logging structures in PKT 3.1 format struct LogHeader { char Signature[3]; @@ -27,11 +28,19 @@ struct LogHeader struct PacketHeader { - char Direction[4]; + // used to uniquely identify a connection + struct OptionalData + { + uint8 SocketIPBytes[16]; + uint32 SocketPort; + }; + + uint32 Direction; uint32 ConnectionId; uint32 ArrivalTicks; uint32 OptionalDataSize; uint32 Length; + OptionalData OptionalData; uint32 Opcode; }; @@ -45,7 +54,9 @@ PacketLog::PacketLog() : _file(nullptr) PacketLog::~PacketLog() { if (_file) + { fclose(_file); + } _file = nullptr; } @@ -60,9 +71,10 @@ void PacketLog::Initialize() { std::string logsDir = sConfigMgr->GetOption<std::string>("LogsDir", ""); - if (!logsDir.empty()) - if ((logsDir.at(logsDir.length() - 1) != '/') && (logsDir.at(logsDir.length() - 1) != '\\')) - logsDir.push_back('/'); + if (!logsDir.empty() && (logsDir.at(logsDir.length() - 1) != '/') && (logsDir.at(logsDir.length() - 1) != '\\')) + { + logsDir.push_back('/'); + } std::string logname = sConfigMgr->GetOption<std::string>("PacketLogFile", ""); if (!logname.empty()) @@ -80,23 +92,42 @@ void PacketLog::Initialize() header.SniffStartTicks = getMSTime(); header.OptionalDataSize = 0; - fwrite(&header, sizeof(header), 1, _file); + if (CanLogPacket()) + { + fwrite(&header, sizeof(header), 1, _file); + } } } -void PacketLog::LogPacket(WorldPacket const& packet, Direction direction) +void PacketLog::LogPacket(WorldPacket const& packet, Direction direction, boost::asio::ip::address const& addr, uint16 port) { std::lock_guard<std::mutex> lock(_logPacketLock); PacketHeader header; - *reinterpret_cast<uint32*>(header.Direction) = direction == CLIENT_TO_SERVER ? 0x47534d43 : 0x47534d53; + header.Direction = direction == CLIENT_TO_SERVER ? 0x47534d43 : 0x47534d53; header.ConnectionId = 0; header.ArrivalTicks = getMSTime(); - header.OptionalDataSize = 0; - header.Length = packet.size() + 4; + + header.OptionalDataSize = sizeof(header.OptionalData); + memset(header.OptionalData.SocketIPBytes, 0, sizeof(header.OptionalData.SocketIPBytes)); + + if (addr.is_v4()) + { + auto bytes = addr.to_v4().to_bytes(); + memcpy(header.OptionalData.SocketIPBytes, bytes.data(), bytes.size()); + } + else if (addr.is_v6()) + { + auto bytes = addr.to_v6().to_bytes(); + memcpy(header.OptionalData.SocketIPBytes, bytes.data(), bytes.size()); + } + + header.OptionalData.SocketPort = port; + header.Length = packet.size() + sizeof(header.Opcode); header.Opcode = packet.GetOpcode(); fwrite(&header, sizeof(header), 1, _file); + if (!packet.empty()) { fwrite(packet.contents(), 1, packet.size(), _file); diff --git a/src/server/game/Server/Protocol/PacketLog.h b/src/server/game/Server/Protocol/PacketLog.h index 5d2bcbaf09..f86cd3ecc7 100644 --- a/src/server/game/Server/Protocol/PacketLog.h +++ b/src/server/game/Server/Protocol/PacketLog.h @@ -8,6 +8,7 @@ #define ACORE_PACKETLOG_H #include "Common.h" +#include <boost/asio/ip/address.hpp> #include <mutex> enum Direction @@ -18,7 +19,7 @@ enum Direction class WorldPacket; -class PacketLog +class AC_GAME_API PacketLog { private: PacketLog(); @@ -31,7 +32,7 @@ public: void Initialize(); bool CanLogPacket() const { return (_file != nullptr); } - void LogPacket(WorldPacket const& packet, Direction direction); + void LogPacket(WorldPacket const& packet, Direction direction, boost::asio::ip::address const& addr, uint16 port); private: FILE* _file; diff --git a/src/server/game/Server/Protocol/ServerPktHeader.h b/src/server/game/Server/Protocol/ServerPktHeader.h new file mode 100644 index 0000000000..ebbc777d11 --- /dev/null +++ b/src/server/game/Server/Protocol/ServerPktHeader.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + */ + +#ifndef __SERVERPKTHDR_H__ +#define __SERVERPKTHDR_H__ + +#include "Log.h" + +#pragma pack(push, 1) + +struct ServerPktHeader +{ + /** + * size is the length of the payload _plus_ the length of the opcode + */ + ServerPktHeader(uint32 size, uint16 cmd) : size(size) + { + uint8 headerIndex=0; + if (isLargePacket()) + { + LOG_DEBUG("network", "initializing large server to client packet. Size: %u, cmd: %u", size, cmd); + header[headerIndex++] = 0x80 | (0xFF & (size >> 16)); + } + header[headerIndex++] = 0xFF &(size >> 8); + header[headerIndex++] = 0xFF & size; + + header[headerIndex++] = 0xFF & cmd; + header[headerIndex++] = 0xFF & (cmd >> 8); + } + + uint8 getHeaderLength() + { + // cmd = 2 bytes, size= 2||3bytes + return 2 + (isLargePacket() ? 3 : 2); + } + + bool isLargePacket() const + { + return size > 0x7FFF; + } + + const uint32 size; + uint8 header[5]; +}; + +#pragma pack(pop) + +#endif diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 01a6e1ed26..2d3837bbde 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -8,7 +8,7 @@ \ingroup u2w */ -#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it +#include "WorldSession.h" #include "AccountMgr.h" #include "BattlegroundMgr.h" #include "Common.h" @@ -25,6 +25,7 @@ #include "PacketUtilities.h" #include "Pet.h" #include "Player.h" +#include "QueryHolder.h" #include "SavingSystem.h" #include "ScriptMgr.h" #include "SocialMgr.h" @@ -34,9 +35,8 @@ #include "WardenWin.h" #include "World.h" #include "WorldPacket.h" -#include "WorldSession.h" -#include "QueryHolder.h" -#include "zlib.h" +#include "WorldSocket.h" +#include <zlib.h> #ifdef ELUNA #include "LuaEngine.h" @@ -91,7 +91,8 @@ bool WorldSessionFilter::Process(WorldPacket* packet) } /// WorldSession constructor -WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime) : +WorldSession::WorldSession(uint32 id, std::string&& name, std::shared_ptr<WorldSocket> sock, AccountTypes sec, uint8 expansion, + time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime) : m_muteTime(mute_time), m_timeOutTime(0), _lastAuctionListItemsMSTime(0), @@ -103,6 +104,7 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 _security(sec), _skipQueue(skipQueue), _accountId(id), + _accountName(std::move(name)), m_expansion(expansion), m_total_time(TotalTime), _logoutTime(0), @@ -125,7 +127,6 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 { memset(m_Tutorials, 0, sizeof(m_Tutorials)); - _warden = nullptr; _offlineTime = 0; _kicked = false; _shouldSetOfflineInDB = true; @@ -135,10 +136,9 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 if (sock) { - m_Address = sock->GetRemoteAddress(); - sock->AddReference(); + m_Address = sock->GetRemoteIpAddress().to_string(); ResetTimeOutTime(false); - LoginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = %u;", GetAccountId()); + LoginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = %u;", GetAccountId()); // One-time query } } @@ -154,17 +154,10 @@ WorldSession::~WorldSession() /// - If have unclosed socket, close it if (m_Socket) { - m_Socket->CloseSocket("WorldSession destructor"); - m_Socket->RemoveReference(); + m_Socket->CloseSocket(); m_Socket = nullptr; } - if (_warden) - { - delete _warden; - _warden = nullptr; - } - ///- empty incoming packet queue WorldPacket* packet = nullptr; while (_recvQueue.next(packet)) @@ -257,9 +250,7 @@ void WorldSession::SendPacket(WorldPacket const* packet) #endif LOG_TRACE("network.opcode", "S->C: %s %s", GetPlayerInfo().c_str(), GetOpcodeNameForLogging(static_cast<OpcodeServer>(packet->GetOpcode())).c_str()); - - if (m_Socket->SendPacket(*packet) == -1) - m_Socket->CloseSocket("m_Socket->SendPacket(*packet) == -1"); + m_Socket->SendPacket(*packet); } /// Add an incoming packet to the queue @@ -290,26 +281,28 @@ void WorldSession::LogUnprocessedTail(WorldPacket* packet) /// Update the WorldSession (triggered by World update) bool WorldSession::Update(uint32 diff, PacketFilter& updater) { + ///- Before we process anything: + /// If necessary, kick the player because the client didn't send anything for too long + /// (or they've been idling in character select) + if (sWorld->getBoolConfig(CONFIG_CLOSE_IDLE_CONNECTIONS) && IsConnectionIdle() && m_Socket) + m_Socket->CloseSocket(); + if (updater.ProcessUnsafe()) - { UpdateTimeOutTime(diff); - /// If necessary, kick the player because the client didn't send anything for too long - /// (or they've been idling in character select) - if (sWorld->getBoolConfig(CONFIG_CLOSE_IDLE_CONNECTIONS) && IsConnectionIdle() && m_Socket) - m_Socket->CloseSocket("Client didn't send anything for too long"); - } - HandleTeleportTimeout(updater.ProcessUnsafe()); - uint32 _startMSTime = getMSTime(); + ///- Retrieve packets from the receive queue and call the appropriate handlers + /// not process packets if socket already closed WorldPacket* packet = nullptr; + + //! Delete packet after processing by default bool deletePacket = true; - WorldPacket* firstDelayedPacket = nullptr; + std::vector<WorldPacket*> requeuePackets; uint32 processedPackets = 0; time_t currentTime = time(nullptr); - while (m_Socket && !m_Socket->IsClosed() && !_recvQueue.empty() && _recvQueue.peek(true) != firstDelayedPacket && _recvQueue.next(packet, updater)) + while (m_Socket && _recvQueue.next(packet, updater)) { OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode()); ClientOpcodeHandler const* opHandle = opcodeTable[opcode]; @@ -412,30 +405,47 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) //Any leftover will be processed in next update if (processedPackets > MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE) break; + } - if (getMSTimeDiff(_startMSTime, getMSTime()) >= 3) // limit (by time) packets processed in one update, prevent DDoS - break; + _recvQueue.readd(requeuePackets.begin(), requeuePackets.end()); + + if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter + { + // Send time sync packet every 10s. + if (_timeSyncTimer > 0) + { + if (diff >= _timeSyncTimer) + { + SendTimeSync(); + } + else + { + _timeSyncTimer -= diff; + } + } } - if (m_Socket && !m_Socket->IsClosed()) - ProcessQueryCallbacks(); + ProcessQueryCallbacks(); + //check if we are safe to proceed with logout + //logout procedure should happen only in World::UpdateSessions() method!!! if (updater.ProcessUnsafe()) { - if (m_Socket && !m_Socket->IsClosed() && _warden) + if (m_Socket && m_Socket->IsOpen() && _warden) { _warden->Update(diff); } - time_t currTime = time(nullptr); - if (ShouldLogOut(currTime) && !m_playerLoading) + if (ShouldLogOut(currentTime) && !m_playerLoading) { LogoutPlayer(true); } - if (m_Socket && m_Socket->IsClosed()) + if (m_Socket && !m_Socket->IsOpen()) { - m_Socket->RemoveReference(); + if (GetPlayer() && _warden) + _warden->Update(diff); + m_Socket = nullptr; } @@ -445,45 +455,30 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) } } - if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter - { - // Send time sync packet every 10s. - if (_timeSyncTimer > 0) - { - if (diff >= _timeSyncTimer) - { - SendTimeSync(); - } - else - { - _timeSyncTimer -= diff; - } - } - } - return true; } bool WorldSession::HandleSocketClosed() { - if (m_Socket && m_Socket->IsClosed() && !IsKicked() && GetPlayer() && !PlayerLogout() && GetPlayer()->m_taxi.empty() && GetPlayer()->IsInWorld() && !World::IsStopped()) + if (m_Socket && !m_Socket->IsOpen() && !IsKicked() && GetPlayer() && !PlayerLogout() && GetPlayer()->m_taxi.empty() && GetPlayer()->IsInWorld() && !World::IsStopped()) { - m_Socket->RemoveReference(); m_Socket = nullptr; GetPlayer()->TradeCancel(false); return true; } + return false; } -bool WorldSession::IsSocketClosed() const { - return !m_Socket || m_Socket->IsClosed(); +bool WorldSession::IsSocketClosed() const +{ + return !m_Socket || !m_Socket->IsOpen(); } void WorldSession::HandleTeleportTimeout(bool updateInSessions) { // pussywizard: handle teleport ack timeout - if (m_Socket && !m_Socket->IsClosed() && GetPlayer() && GetPlayer()->IsBeingTeleported()) + if (m_Socket && m_Socket->IsOpen() && GetPlayer() && GetPlayer()->IsBeingTeleported()) { time_t currTime = time(nullptr); if (updateInSessions) // session update from World::UpdateSessions @@ -681,7 +676,12 @@ void WorldSession::LogoutPlayer(bool save) void WorldSession::KickPlayer(std::string const& reason, bool setKicked) { if (m_Socket) - m_Socket->CloseSocket(reason); + { + LOG_INFO("network.kick", "Account: %u Character: '%s' %s kicked with reason: %s", GetAccountId(), _player ? _player->GetName().c_str() : "<none>", + _player ? _player->GetGUID().ToString().c_str() : "", reason.c_str()); + + m_Socket->CloseSocket(); + } if (setKicked) SetKicked(true); // pussywizard: the session won't be left ingame for 60 seconds and to also kick offline session @@ -947,41 +947,41 @@ void WorldSession::ReadMovementInfo(WorldPacket& data, MovementInfo* mi) It will freeze clients that receive this player's movement info. */ REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ROOT), - MOVEMENTFLAG_ROOT); + MOVEMENTFLAG_ROOT); //! Cannot hover without SPELL_AURA_HOVER REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_HOVER) && !GetPlayer()->HasAuraType(SPELL_AURA_HOVER), - MOVEMENTFLAG_HOVER); + MOVEMENTFLAG_HOVER); //! Cannot ascend and descend at the same time REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ASCENDING) && mi->HasMovementFlag(MOVEMENTFLAG_DESCENDING), - MOVEMENTFLAG_ASCENDING | MOVEMENTFLAG_DESCENDING); + MOVEMENTFLAG_ASCENDING | MOVEMENTFLAG_DESCENDING); //! Cannot move left and right at the same time REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_LEFT) && mi->HasMovementFlag(MOVEMENTFLAG_RIGHT), - MOVEMENTFLAG_LEFT | MOVEMENTFLAG_RIGHT); + MOVEMENTFLAG_LEFT | MOVEMENTFLAG_RIGHT); //! Cannot strafe left and right at the same time REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_STRAFE_LEFT) && mi->HasMovementFlag(MOVEMENTFLAG_STRAFE_RIGHT), - MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT); + MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT); //! Cannot pitch up and down at the same time REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_PITCH_UP) && mi->HasMovementFlag(MOVEMENTFLAG_PITCH_DOWN), - MOVEMENTFLAG_PITCH_UP | MOVEMENTFLAG_PITCH_DOWN); + MOVEMENTFLAG_PITCH_UP | MOVEMENTFLAG_PITCH_DOWN); //! Cannot move forwards and backwards at the same time REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FORWARD) && mi->HasMovementFlag(MOVEMENTFLAG_BACKWARD), - MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD); + MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD); //! Cannot walk on water without SPELL_AURA_WATER_WALK REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_WATERWALKING) && - !GetPlayer()->HasAuraType(SPELL_AURA_WATER_WALK) && - !GetPlayer()->HasAuraType(SPELL_AURA_GHOST), - MOVEMENTFLAG_WATERWALKING); + !GetPlayer()->HasAuraType(SPELL_AURA_WATER_WALK) && + !GetPlayer()->HasAuraType(SPELL_AURA_GHOST), + MOVEMENTFLAG_WATERWALKING); //! Cannot feather fall without SPELL_AURA_FEATHER_FALL REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FALLING_SLOW) && !GetPlayer()->HasAuraType(SPELL_AURA_FEATHER_FALL), - MOVEMENTFLAG_FALLING_SLOW); + MOVEMENTFLAG_FALLING_SLOW); /*! Cannot fly if no fly auras present. Exception is being a GM. Note that we check for account level instead of Player::IsGameMaster() because in some @@ -990,14 +990,14 @@ void WorldSession::ReadMovementInfo(WorldPacket& data, MovementInfo* mi) */ REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY) && GetSecurity() == SEC_PLAYER && !GetPlayer()->m_mover->HasAuraType(SPELL_AURA_FLY) && !GetPlayer()->m_mover->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED), - MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY); + MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY); //! Cannot fly and fall at the same time REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY) && mi->HasMovementFlag(MOVEMENTFLAG_FALLING), - MOVEMENTFLAG_FALLING); + MOVEMENTFLAG_FALLING); REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_SPLINE_ENABLED) && - (!GetPlayer()->movespline->Initialized() || GetPlayer()->movespline->Finalized()), MOVEMENTFLAG_SPLINE_ENABLED); + (!GetPlayer()->movespline->Initialized() || GetPlayer()->movespline->Finalized()), MOVEMENTFLAG_SPLINE_ENABLED); #undef REMOVE_VIOLATING_FLAGS } @@ -1040,7 +1040,7 @@ void WorldSession::WriteMovementInfo(WorldPacket* data, MovementInfo* mi) *data << mi->splineElevation; } -void WorldSession::ReadAddonsInfo(WorldPacket& data) +void WorldSession::ReadAddonsInfo(ByteBuffer& data) { if (data.rpos() + 4 > data.size()) return; @@ -1218,7 +1218,7 @@ void WorldSession::InitWarden(SessionKey const& k, std::string const& os) { if (os == "Win") { - _warden = new WardenWin(); + _warden = std::make_unique<WardenWin>(); _warden->Init(this, k); } else if (os == "OSX") @@ -1533,6 +1533,9 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co return maxPacketCounterAllowed; } +WorldSession::DosProtection::DosProtection(WorldSession* s) : + Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY)) { } + void WorldSession::ResetTimeSync() { _timeSyncNextCounter = 0; diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 49664d75db..9f605c6e1f 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -230,14 +230,14 @@ struct PacketCounter class WorldSession { public: - WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime); + WorldSession(uint32 id, std::string&& name, std::shared_ptr<WorldSocket> sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime); ~WorldSession(); bool PlayerLoading() const { return m_playerLoading; } bool PlayerLogout() const { return m_playerLogout; } bool PlayerLogoutWithSave() const { return m_playerLogout && m_playerSave; } - void ReadAddonsInfo(WorldPacket& data); + void ReadAddonsInfo(ByteBuffer& data); void SendAddonsInfo(); void ReadMovementInfo(WorldPacket& data, MovementInfo* mi); @@ -987,7 +987,7 @@ protected: { friend class World; public: - DosProtection(WorldSession* s) : Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY)) { } + DosProtection(WorldSession* s); bool EvaluateOpcode(WorldPacket& p, time_t time) const; protected: enum Policy @@ -1035,20 +1035,20 @@ private: ObjectGuid::LowType m_GUIDLow; Player* _player; - WorldSocket* m_Socket; + std::shared_ptr<WorldSocket> m_Socket; std::string m_Address; - // std::string m_LAddress; // Last Attempted Remote Adress - we can not set attempted ip for a non-existing session! AccountTypes _security; bool _skipQueue; uint32 _accountId; + std::string _accountName; uint8 m_expansion; uint32 m_total_time; typedef std::list<AddonInfo> AddonsList; // Warden - Warden* _warden; // Remains nullptr if Warden system is not enabled by config + std::unique_ptr<Warden> _warden; // Remains nullptr if Warden system is not enabled by config time_t _logoutTime; bool m_inQueue; // session wait in auth.queue @@ -1057,7 +1057,7 @@ private: bool m_playerSave; LocaleConstant m_sessionDbcLocale; LocaleConstant m_sessionDbLocaleIndex; - uint32 m_latency; + std::atomic<uint32> m_latency; AccountData m_accountData[NUM_ACCOUNT_DATA_TYPES]; uint32 m_Tutorials[MAX_ACCOUNT_TUTORIAL_VALUES]; bool m_TutorialsChanged; @@ -1081,6 +1081,9 @@ private: std::map<uint32, uint32> _pendingTimeSyncRequests; // key: counter. value: server time when packet with that counter was sent. uint32 _timeSyncNextCounter; uint32 _timeSyncTimer; + + WorldSession(WorldSession const& right) = delete; + WorldSession& operator=(WorldSession const& right) = delete; }; #endif /// @} diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp index 0dee40c2ba..032f95e13e 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -4,914 +4,511 @@ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ +#include "WorldSocket.h" #include "AccountMgr.h" -#include "ByteBuffer.h" -#include "Common.h" +#include "BigNumber.h" #include "CryptoHash.h" #include "CryptoRandom.h" #include "DatabaseEnv.h" #include "IPLocation.h" -#include "Log.h" #include "Opcodes.h" #include "PacketLog.h" -#include "Player.h" +#include "Random.h" #include "Realm.h" #include "ScriptMgr.h" -#include "SharedDefines.h" -#include "Util.h" #include "World.h" -#include "WorldPacket.h" #include "WorldSession.h" -#include "WorldSocket.h" -#include "WorldSocketMgr.h" -#include <ace/Message_Block.h> -#include <ace/OS_NS_string.h> -#include <ace/OS_NS_unistd.h> -#include <ace/Reactor.h> -#include <ace/os_include/arpa/os_inet.h> -#include <ace/os_include/netinet/os_tcp.h> -#include <ace/os_include/sys/os_socket.h> -#include <ace/os_include/sys/os_types.h> -#include <thread> - -#ifdef ELUNA -#include "LuaEngine.h" -#endif - -#if defined(__GNUC__) -#pragma pack(1) -#else -#pragma pack(push, 1) -#endif - -struct ServerPktHeader -{ - /** - * size is the length of the payload _plus_ the length of the opcode - */ - ServerPktHeader(uint32 size, uint16 cmd) : size(size) - { - uint8 headerIndex = 0; - if (isLargePacket()) - { - LOG_DEBUG("network", "initializing large server to client packet. Size: %u, cmd: %u", size, cmd); - header[headerIndex++] = 0x80 | (0xFF & (size >> 16)); - } - header[headerIndex++] = 0xFF & (size >> 8); - header[headerIndex++] = 0xFF & size; - - header[headerIndex++] = 0xFF & cmd; - header[headerIndex++] = 0xFF & (cmd >> 8); - } - - uint8 getHeaderLength() - { - // cmd = 2 bytes, size= 2||3bytes - return 2 + (isLargePacket() ? 3 : 2); - } - - bool isLargePacket() const - { - return size > 0x7FFF; - } - - const uint32 size; - uint8 header[5]; -}; - -struct ClientPktHeader -{ - uint16 size; - uint32 cmd; -}; - -#if defined(__GNUC__) -#pragma pack() -#else -#pragma pack(pop) -#endif - -struct AccountInfo -{ - uint32 Id; - ::SessionKey SessionKey; - std::string LastIP; - bool IsLockedToIP; - std::string LockCountry; - uint8 Expansion; - int64 MuteTime; - LocaleConstant Locale; - uint32 Recruiter; - std::string OS; - bool IsRectuiter; - AccountTypes Security; - bool IsBanned; - uint32 TotalTime; - - explicit AccountInfo(Field* fields) - { - // 0 1 2 3 4 5 6 7 8 9 10 11 - // SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, aa.gmLevel, - // 12 13 - // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id - // FROM account a - // LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) - // LEFT JOIN account_banned ab ON a.id = ab.id - // LEFT JOIN account r ON a.id = r.recruiter - // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1 - Id = fields[0].GetUInt32(); - SessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>(); - LastIP = fields[2].GetString(); - IsLockedToIP = fields[3].GetBool(); - LockCountry = fields[4].GetString(); - Expansion = fields[5].GetUInt8(); - MuteTime = fields[6].GetInt64(); - Locale = LocaleConstant(fields[7].GetUInt8()); - Recruiter = fields[8].GetUInt32(); - OS = fields[9].GetString(); - TotalTime = fields[10].GetUInt32(); - Security = AccountTypes(fields[11].GetUInt8()); - IsBanned = fields[12].GetUInt64() != 0; - IsRectuiter = fields[13].GetUInt32() != 0; - - uint32 world_expansion = sWorld->getIntConfig(CONFIG_EXPANSION); - if (Expansion > world_expansion) - Expansion = world_expansion; - - if (Locale >= TOTAL_LOCALES) - Locale = LOCALE_enUS; - } -}; - -WorldSocket::WorldSocket(void): WorldHandler(), - m_LastPingTime(SystemTimePoint::min()), m_OverSpeedPings(0), m_Session(0), - m_RecvWPct(0), m_RecvPct(), m_Header(sizeof (ClientPktHeader)), - m_OutBuffer(0), m_OutBufferSize(65536), m_OutActive(false) -{ - Acore::Crypto::GetRandomBytes(m_Seed); - - reference_counting_policy().value (ACE_Event_Handler::Reference_Counting_Policy::ENABLED); +#include <memory> - msg_queue()->high_water_mark(8 * 1024 * 1024); - msg_queue()->low_water_mark(8 * 1024 * 1024); -} +using boost::asio::ip::tcp; -WorldSocket::~WorldSocket(void) +WorldSocket::WorldSocket(tcp::socket&& socket) + : Socket(std::move(socket)), _OverSpeedPings(0), _worldSession(nullptr), _authed(false), _sendBufferSize(4096) { - delete m_RecvWPct; - - if (m_OutBuffer) - m_OutBuffer->release(); - - closing_ = true; - - peer().close(); + Acore::Crypto::GetRandomBytes(_authSeed); + _headerBuffer.Resize(sizeof(ClientPktHeader)); } -bool WorldSocket::IsClosed(void) const -{ - return closing_; -} +WorldSocket::~WorldSocket() = default; -void WorldSocket::CloseSocket(std::string const& reason) +void WorldSocket::Start() { - if (!reason.empty()) - LOG_DEBUG("network", "Socket closed because of: %s", reason.c_str()); + std::string ip_address = GetRemoteIpAddress().to_string(); - { - std::lock_guard<std::mutex> guard(m_OutBufferLock); - - if (closing_) - return; + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO); + stmt->setString(0, ip_address); - closing_ = true; - peer().close_writer(); - } - - { - std::lock_guard<std::mutex> guard(m_SessionLock); - - m_Session = nullptr; - } -} - -const std::string& WorldSocket::GetRemoteAddress(void) const -{ - return m_Address; + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSocket::CheckIpCallback, this, std::placeholders::_1))); } -int WorldSocket::SendPacket(WorldPacket const& pct) +void WorldSocket::CheckIpCallback(PreparedQueryResult result) { - std::lock_guard<std::mutex> guard(m_OutBufferLock); - - if (closing_) - return -1; - - // Dump outgoing packet. - if (sPacketLog->CanLogPacket()) - sPacketLog->LogPacket(pct, SERVER_TO_CLIENT); - - ServerPktHeader header(pct.size() + 2, pct.GetOpcode()); - - if (m_Crypt.IsInitialized()) - m_Crypt.EncryptSend((uint8*)header.header, header.getHeaderLength()); - - if (m_OutBuffer->space() >= pct.size() + header.getHeaderLength() && msg_queue()->is_empty()) + if (result) { - // Put the packet on the buffer. - if (m_OutBuffer->copy((char*) header.header, header.getHeaderLength()) == -1) - ABORT(); - - if (!pct.empty()) - if (m_OutBuffer->copy((char*) pct.contents(), pct.size()) == -1) - ABORT(); - } - else - { - // Enqueue the packet. - ACE_Message_Block* mb; - - ACE_NEW_RETURN(mb, ACE_Message_Block(pct.size() + header.getHeaderLength()), -1); - - mb->copy((char*) header.header, header.getHeaderLength()); + bool banned = false; + do + { + Field* fields = result->Fetch(); + if (fields[0].GetUInt64() != 0) + banned = true; - if (!pct.empty()) - mb->copy((const char*)pct.contents(), pct.size()); + } while (result->NextRow()); - if (msg_queue()->enqueue_tail(mb, (ACE_Time_Value*)&ACE_Time_Value::zero) == -1) + if (banned) { - LOG_ERROR("network", "WorldSocket::SendPacket enqueue_tail failed"); - mb->release(); - return -1; + SendAuthResponseError(AUTH_REJECT); + LOG_ERROR("network", "WorldSocket::CheckIpCallback: Sent Auth Response (IP %s banned).", GetRemoteIpAddress().to_string().c_str()); + DelayedCloseSocket(); + return; } } - return 0; -} - -long WorldSocket::AddReference(void) -{ - return static_cast<long> (add_reference()); + AsyncRead(); + HandleSendAuthSession(); } -long WorldSocket::RemoveReference(void) +bool WorldSocket::Update() { - return static_cast<long> (remove_reference()); -} - -int WorldSocket::open(void* a) -{ - ACE_UNUSED_ARG (a); - - // Prevent double call to this func. - if (m_OutBuffer) - return -1; - - // This will also prevent the socket from being Updated - // while we are initializing it. - m_OutActive = true; - - // Hook for the manager. - if (sWorldSocketMgr->OnSocketOpen(this) == -1) - return -1; - - // Allocate the buffer. - ACE_NEW_RETURN (m_OutBuffer, ACE_Message_Block (m_OutBufferSize), -1); - - // Store peer address. - ACE_INET_Addr remote_addr; - - if (peer().get_remote_addr(remote_addr) == -1) + EncryptablePacket* queued; + MessageBuffer buffer(_sendBufferSize); + while (_bufferQueue.Dequeue(queued)) { - LOG_ERROR("network", "WorldSocket::open: peer().get_remote_addr errno = %s", ACE_OS::strerror (errno)); - return -1; - } + ServerPktHeader header(queued->size() + 2, queued->GetOpcode()); + if (queued->NeedsEncryption()) + _authCrypt.EncryptSend(header.header, header.getHeaderLength()); - m_Address = remote_addr.get_host_addr(); + if (buffer.GetRemainingSpace() < queued->size() + header.getHeaderLength()) + { + QueuePacket(std::move(buffer)); + buffer.Resize(_sendBufferSize); + } - // Send startup packet. - WorldPacket packet (SMSG_AUTH_CHALLENGE, 24); - packet << uint32(1); // 1...31 - packet.append(m_Seed); - packet.append(Acore::Crypto::GetRandomBytes<32>()); // new encryption seeds + if (buffer.GetRemainingSpace() >= queued->size() + header.getHeaderLength()) + { + buffer.Write(header.header, header.getHeaderLength()); + if (!queued->empty()) + buffer.Write(queued->contents(), queued->size()); + } + else // single packet larger than 4096 bytes + { + MessageBuffer packetBuffer(queued->size() + header.getHeaderLength()); + packetBuffer.Write(header.header, header.getHeaderLength()); + if (!queued->empty()) + packetBuffer.Write(queued->contents(), queued->size()); - if (SendPacket(packet) == -1) - return -1; + QueuePacket(std::move(packetBuffer)); + } - // Register with ACE Reactor - if (reactor()->register_handler(this, ACE_Event_Handler::READ_MASK | ACE_Event_Handler::WRITE_MASK) == -1) - { - LOG_ERROR("network", "WorldSocket::open: unable to register client handler errno = %s", ACE_OS::strerror (errno)); - return -1; + delete queued; } - // reactor takes care of the socket from now on - remove_reference(); + if (buffer.GetActiveSize() > 0) + QueuePacket(std::move(buffer)); - return 0; -} - -int WorldSocket::close(u_long) -{ - shutdown(); + if (!BaseSocket::Update()) + return false; - closing_ = true; + _queryProcessor.ProcessReadyCallbacks(); - remove_reference(); - - return 0; + return true; } -int WorldSocket::handle_input(ACE_HANDLE) +void WorldSocket::HandleSendAuthSession() { - if (closing_) - return -1; - - switch (handle_input_missing_data()) - { - case -1 : - { - if ((errno == EWOULDBLOCK) || - (errno == EAGAIN)) - { - return Update(); // interesting line, isn't it ? - } - - LOG_DEBUG("network", "WorldSocket::handle_input: Peer error closing connection errno = %s", ACE_OS::strerror (errno)); - - errno = ECONNRESET; - return -1; - } - case 0: - { - LOG_DEBUG("network", "WorldSocket::handle_input: Peer has closed connection"); + WorldPacket packet(SMSG_AUTH_CHALLENGE, 40); + packet << uint32(1); // 1...31 + packet.append(_authSeed); - errno = ECONNRESET; - return -1; - } - case 1: - return 1; - default: - return Update(); // another interesting line ;) - } + packet.append(Acore::Crypto::GetRandomBytes<32>()); // new encryption seeds - return -1; + SendPacketAndLogOpcode(packet); } -int WorldSocket::handle_output(ACE_HANDLE) +void WorldSocket::OnClose() { - if (closing_) - return -1; - - std::lock_guard<std::mutex> guard(m_OutBufferLock); - - size_t send_len = m_OutBuffer->length(); - - if (send_len == 0) - return handle_output_queue(); - -#ifdef MSG_NOSIGNAL - ssize_t n = peer().send (m_OutBuffer->rd_ptr(), send_len, MSG_NOSIGNAL); -#else - ssize_t n = peer().send (m_OutBuffer->rd_ptr(), send_len); -#endif // MSG_NOSIGNAL - - if (n == 0) - return -1; - else if (n == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) - return schedule_wakeup_output(); - - return -1; + std::lock_guard<std::mutex> sessionGuard(_worldSessionLock); + _worldSession = nullptr; } - else if (n < (ssize_t)send_len) //now n > 0 - { - m_OutBuffer->rd_ptr (static_cast<size_t> (n)); - - // move the data to the base of the buffer - m_OutBuffer->crunch(); - - return schedule_wakeup_output(); - } - else //now n == send_len - { - m_OutBuffer->reset(); - - return handle_output_queue(); - } - - ACE_NOTREACHED (return 0); } -int WorldSocket::handle_output_queue() +void WorldSocket::ReadHandler() { - if (msg_queue()->is_empty()) - return cancel_wakeup_output(); - - ACE_Message_Block* mblk; + if (!IsOpen()) + return; - if (msg_queue()->dequeue_head(mblk, (ACE_Time_Value*)&ACE_Time_Value::zero) == -1) + MessageBuffer& packet = GetReadBuffer(); + while (packet.GetActiveSize() > 0) { - LOG_ERROR("network", "WorldSocket::handle_output_queue dequeue_head"); - return -1; - } - - const size_t send_len = mblk->length(); + if (_headerBuffer.GetRemainingSpace() > 0) + { + // need to receive the header + std::size_t readHeaderSize = std::min(packet.GetActiveSize(), _headerBuffer.GetRemainingSpace()); + _headerBuffer.Write(packet.GetReadPointer(), readHeaderSize); + packet.ReadCompleted(readHeaderSize); -#ifdef MSG_NOSIGNAL - ssize_t n = peer().send(mblk->rd_ptr(), send_len, MSG_NOSIGNAL); -#else - ssize_t n = peer().send(mblk->rd_ptr(), send_len); -#endif // MSG_NOSIGNAL + if (_headerBuffer.GetRemainingSpace() > 0) + { + // Couldn't receive the whole header this time. + ASSERT(packet.GetActiveSize() == 0); + break; + } - if (n == 0) - { - mblk->release(); + // We just received nice new header + if (!ReadHeaderHandler()) + { + CloseSocket(); + return; + } + } - return -1; - } - else if (n == -1) - { - if (errno == EWOULDBLOCK || errno == EAGAIN) + // We have full read header, now check the data payload + if (_packetBuffer.GetRemainingSpace() > 0) { - msg_queue()->enqueue_head(mblk, (ACE_Time_Value*) &ACE_Time_Value::zero); - return schedule_wakeup_output(); + // need more data in the payload + std::size_t readDataSize = std::min(packet.GetActiveSize(), _packetBuffer.GetRemainingSpace()); + _packetBuffer.Write(packet.GetReadPointer(), readDataSize); + packet.ReadCompleted(readDataSize); + + if (_packetBuffer.GetRemainingSpace() > 0) + { + // Couldn't receive the whole data this time. + ASSERT(packet.GetActiveSize() == 0); + break; + } } - mblk->release(); - return -1; - } - else if (n < (ssize_t)send_len) //now n > 0 - { - mblk->rd_ptr(static_cast<size_t> (n)); + // just received fresh new payload + ReadDataHandlerResult result = ReadDataHandler(); + _headerBuffer.Reset(); - if (msg_queue()->enqueue_head(mblk, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1) + if (result != ReadDataHandlerResult::Ok) { - LOG_ERROR("network", "WorldSocket::handle_output_queue enqueue_head"); - mblk->release(); - return -1; - } - - return schedule_wakeup_output(); - } - else //now n == send_len - { - mblk->release(); + if (result != ReadDataHandlerResult::WaitingForQuery) + { + CloseSocket(); + } - return msg_queue()->is_empty() ? cancel_wakeup_output() : ACE_Event_Handler::WRITE_MASK; + return; + } } - return -1; + AsyncRead(); } -int WorldSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask) +bool WorldSocket::ReadHeaderHandler() { - // Critical section - { - std::lock_guard<std::mutex> guard(m_OutBufferLock); + ASSERT(_headerBuffer.GetActiveSize() == sizeof(ClientPktHeader)); - closing_ = true; - - if (h == ACE_INVALID_HANDLE) - peer().close_writer(); - } - - // Critical section + if (_authCrypt.IsInitialized()) { - std::lock_guard<decltype(m_SessionLock)> guard(m_SessionLock); - - m_Session = nullptr; + _authCrypt.DecryptRecv(_headerBuffer.GetReadPointer(), sizeof(ClientPktHeader)); } - reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK); - return 0; -} - -int WorldSocket::Update(void) -{ - if (closing_) - return -1; - - if (m_OutActive) - return 0; + ClientPktHeader* header = reinterpret_cast<ClientPktHeader*>(_headerBuffer.GetReadPointer()); + EndianConvertReverse(header->size); + EndianConvert(header->cmd); + if (!header->IsValidSize() || !header->IsValidOpcode()) { - std::lock_guard<std::mutex> guard(m_OutBufferLock); + LOG_ERROR("network", "WorldSocket::ReadHeaderHandler(): client %s sent malformed packet (size: %hu, cmd: %u)", + GetRemoteIpAddress().to_string().c_str(), header->size, header->cmd); - if (m_OutBuffer->length() == 0 && msg_queue()->is_empty()) - return 0; + return false; } - int ret; - do - ret = handle_output (get_handle()); - while (ret > 0); + header->size -= sizeof(header->cmd); + _packetBuffer.Resize(header->size); - return ret; + return true; } -int WorldSocket::handle_input_header(void) +struct AuthSession { - ASSERT(m_RecvWPct == nullptr); - ASSERT(m_Header.length() == sizeof(ClientPktHeader)); - - if (m_Crypt.IsInitialized()) - m_Crypt.DecryptRecv((uint8*) m_Header.rd_ptr(), sizeof(ClientPktHeader)); - - ClientPktHeader& header = *((ClientPktHeader*) m_Header.rd_ptr()); + uint32 BattlegroupID = 0; + uint32 LoginServerType = 0; + uint32 RealmID = 0; + uint32 Build = 0; + std::array<uint8, 4> LocalChallenge = {}; + uint32 LoginServerID = 0; + uint32 RegionID = 0; + uint64 DosResponse = 0; + Acore::Crypto::SHA1::Digest Digest = {}; + std::string Account; + ByteBuffer AddonInfo; +}; - EndianConvertReverse(header.size); - EndianConvert(header.cmd); +struct AccountInfo +{ + uint32 Id; + ::SessionKey SessionKey; + std::string LastIP; + bool IsLockedToIP; + std::string LockCountry; + uint8 Expansion; + int64 MuteTime; + LocaleConstant Locale; + uint32 Recruiter; + std::string OS; + bool IsRectuiter; + AccountTypes Security; + bool IsBanned; + uint32 TotalTime; - if ((header.size < 4) || (header.size > 10240) || (header.cmd > 10240)) + explicit AccountInfo(Field* fields) { - LOG_ERROR("network", "WorldSocket::handle_input_header(): client (%s) sent malformed packet (size: %hd, cmd: %d)", - GetRemoteAddress().c_str(), header.size, header.cmd); - - errno = EINVAL; - return -1; - } - - header.size -= 4; + // 0 1 2 3 4 5 6 7 8 9 10 11 + // SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, aa.gmLevel, + // 12 13 + // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id + // FROM account a + // LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) + // LEFT JOIN account_banned ab ON a.id = ab.id + // LEFT JOIN account r ON a.id = r.recruiter + // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1 + Id = fields[0].GetUInt32(); + SessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>(); + LastIP = fields[2].GetString(); + IsLockedToIP = fields[3].GetBool(); + LockCountry = fields[4].GetString(); + Expansion = fields[5].GetUInt8(); + MuteTime = fields[6].GetInt64(); + Locale = LocaleConstant(fields[7].GetUInt8()); + Recruiter = fields[8].GetUInt32(); + OS = fields[9].GetString(); + TotalTime = fields[10].GetUInt32(); + Security = AccountTypes(fields[11].GetUInt8()); + IsBanned = fields[12].GetUInt64() != 0; + IsRectuiter = fields[13].GetUInt32() != 0; - ACE_NEW_RETURN (m_RecvWPct, WorldPacket ((uint16) header.cmd, header.size), -1); + uint32 world_expansion = sWorld->getIntConfig(CONFIG_EXPANSION); + if (Expansion > world_expansion) + Expansion = world_expansion; - if (header.size > 0) - { - m_RecvWPct->resize (header.size); - m_RecvPct.base ((char*) m_RecvWPct->contents(), m_RecvWPct->size()); - } - else - { - ASSERT(m_RecvPct.space() == 0); + if (Locale >= TOTAL_LOCALES) + Locale = LOCALE_enUS; } +}; - return 0; -} - -int WorldSocket::handle_input_payload(void) -{ - // set errno properly here on error !!! - // now have a header and payload - - ASSERT(m_RecvPct.space() == 0); - ASSERT(m_Header.space() == 0); - ASSERT(m_RecvWPct != nullptr); - - const int ret = ProcessIncoming (m_RecvWPct); - - m_RecvPct.base (nullptr, 0); - m_RecvPct.reset(); - m_RecvWPct = nullptr; - - m_Header.reset(); - - if (ret == -1) - errno = EINVAL; - - return ret; -} - -int WorldSocket::handle_input_missing_data(void) +WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler() { - char buf [4096]; + ClientPktHeader* header = reinterpret_cast<ClientPktHeader*>(_headerBuffer.GetReadPointer()); + OpcodeClient opcode = static_cast<OpcodeClient>(header->cmd); - ACE_Data_Block db (sizeof (buf), - ACE_Message_Block::MB_DATA, - buf, - 0, - 0, - ACE_Message_Block::DONT_DELETE, - 0); + WorldPacket packet(opcode, std::move(_packetBuffer)); + WorldPacket* packetToQueue; - ACE_Message_Block message_block(&db, - ACE_Message_Block::DONT_DELETE, - 0); - - const size_t recv_size = message_block.space(); - - const ssize_t n = peer().recv (message_block.wr_ptr(), - recv_size); - - if (n <= 0) - return int(n); + if (sPacketLog->CanLogPacket()) + sPacketLog->LogPacket(packet, CLIENT_TO_SERVER, GetRemoteIpAddress(), GetRemotePort()); - message_block.wr_ptr (n); + std::unique_lock<std::mutex> sessionGuard(_worldSessionLock, std::defer_lock); - while (message_block.length() > 0) + switch (opcode) { - if (m_Header.space() > 0) + case CMSG_PING: { - //need to receive the header - const size_t to_header = (message_block.length() > m_Header.space() ? m_Header.space() : message_block.length()); - m_Header.copy (message_block.rd_ptr(), to_header); - message_block.rd_ptr (to_header); - - if (m_Header.space() > 0) + LogOpcodeText(opcode, sessionGuard); + try { - // Couldn't receive the whole header this time. - ASSERT(message_block.length() == 0); - errno = EWOULDBLOCK; - return -1; + return HandlePing(packet) ? ReadDataHandlerResult::Ok : ReadDataHandlerResult::Error; } - - // We just received nice new header - if (handle_input_header() == -1) + catch (ByteBufferException const&) { - ASSERT((errno != EWOULDBLOCK) && (errno != EAGAIN)); - return -1; } + LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_PING", GetRemoteIpAddress().to_string().c_str()); + return ReadDataHandlerResult::Error; } - - // Its possible on some error situations that this happens - // for example on closing when epoll receives more chunked data and stuff - // hope this is not hack, as proper m_RecvWPct is asserted around - if (!m_RecvWPct) + case CMSG_AUTH_SESSION: { - LOG_ERROR("network", "Forcing close on input m_RecvWPct = nullptr"); - errno = EINVAL; - return -1; - } - - // We have full read header, now check the data payload - if (m_RecvPct.space() > 0) - { - //need more data in the payload - const size_t to_data = (message_block.length() > m_RecvPct.space() ? m_RecvPct.space() : message_block.length()); - m_RecvPct.copy (message_block.rd_ptr(), to_data); - message_block.rd_ptr (to_data); + LogOpcodeText(opcode, sessionGuard); + if (_authed) + { + // locking just to safely log offending user is probably overkill but we are disconnecting him anyway + if (sessionGuard.try_lock()) + LOG_ERROR("network", "WorldSocket::ProcessIncoming: received duplicate CMSG_AUTH_SESSION from %s", _worldSession->GetPlayerInfo().c_str()); + return ReadDataHandlerResult::Error; + } - if (m_RecvPct.space() > 0) + try { - // Couldn't receive the whole data this time. - ASSERT(message_block.length() == 0); - errno = EWOULDBLOCK; - return -1; + HandleAuthSession(packet); + return ReadDataHandlerResult::WaitingForQuery; } - } + catch (ByteBufferException const&) { } - //just received fresh new payload - if (handle_input_payload() == -1) - { - ASSERT((errno != EWOULDBLOCK) && (errno != EAGAIN)); - return -1; + LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_AUTH_SESSION", GetRemoteIpAddress().to_string().c_str()); + return ReadDataHandlerResult::Error; } + case CMSG_KEEP_ALIVE: // todo: handle this packet in the same way of CMSG_TIME_SYNC_RESP + sessionGuard.lock(); + LogOpcodeText(opcode, sessionGuard); + if (_worldSession) + _worldSession->ResetTimeOutTime(true); + return ReadDataHandlerResult::Ok; + case CMSG_TIME_SYNC_RESP: + packetToQueue = new WorldPacket(std::move(packet), std::chrono::steady_clock::now()); + break; + default: + packetToQueue = new WorldPacket(std::move(packet)); + break; } - return size_t(n) == recv_size ? 1 : 2; -} - -int WorldSocket::cancel_wakeup_output() -{ - if (!m_OutActive) - return 0; + sessionGuard.lock(); - m_OutActive = false; + LogOpcodeText(opcode, sessionGuard); - if (reactor()->cancel_wakeup - (this, ACE_Event_Handler::WRITE_MASK) == -1) + if (!_worldSession) { - // would be good to store errno from reactor with errno guard - LOG_ERROR("network", "WorldSocket::cancel_wakeup_output"); - return -1; + LOG_ERROR("network.opcode", "ProcessIncoming: Client not authed opcode = %u", uint32(opcode)); + delete packetToQueue; + return ReadDataHandlerResult::Error; } - return 0; -} - -int WorldSocket::schedule_wakeup_output() -{ - if (m_OutActive) - return 0; - - m_OutActive = true; - - if (reactor()->schedule_wakeup - (this, ACE_Event_Handler::WRITE_MASK) == -1) + OpcodeHandler const* handler = opcodeTable[opcode]; + if (!handler) { - LOG_ERROR("network", "WorldSocket::schedule_wakeup_output"); - return -1; + LOG_ERROR("network.opcode", "No defined handler for opcode %s sent by %s", GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet.GetOpcode())).c_str(), _worldSession->GetPlayerInfo().c_str()); + delete packetToQueue; + return ReadDataHandlerResult::Error; } - return 0; -} - -int WorldSocket::ProcessIncoming(WorldPacket* new_pct) -{ - ASSERT(new_pct); - - // manage memory ;) - std::unique_ptr<WorldPacket> aptr(new_pct); - - OpcodeClient opcode = static_cast<OpcodeClient>(aptr->GetOpcode()); + // Our Idle timer will reset on any non PING opcodes on login screen, allowing us to catch people idling. + _worldSession->ResetTimeOutTime(false); - if (closing_) - return -1; + // Copy the packet to the heap before enqueuing + _worldSession->QueuePacket(packetToQueue); - // Dump received packet. - if (sPacketLog->CanLogPacket()) - sPacketLog->LogPacket(*new_pct, CLIENT_TO_SERVER); + return ReadDataHandlerResult::Ok; +} - try +void WorldSocket::LogOpcodeText(OpcodeClient opcode, std::unique_lock<std::mutex> const& guard) const +{ + if (!guard) { - switch (opcode) - { - case CMSG_PING: - try - { - return HandlePing(*new_pct); - } - catch (ByteBufferPositionException const&) { } - LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client sent malformed CMSG_PING"); - return -1; - case CMSG_AUTH_SESSION: - if (m_Session) - { - LOG_ERROR("network", "WorldSocket::ProcessIncoming: Player send CMSG_AUTH_SESSION again"); - return -1; - } - return HandleAuthSession (*new_pct); - case CMSG_KEEP_ALIVE: - if (m_Session) - m_Session->ResetTimeOutTime(true); - return 0; - case CMSG_TIME_SYNC_RESP: - new_pct = new WorldPacket(std::move(*new_pct), std::chrono::steady_clock::now()); - break; - default: - break; - } + LOG_TRACE("network.opcode", "C->S: %s %s", GetRemoteIpAddress().to_string().c_str(), GetOpcodeNameForLogging(opcode).c_str()); } - catch (ByteBufferException const&) + else { - LOG_ERROR("network", "WorldSocket::ProcessIncoming ByteBufferException occured while parsing an instant handled packet (opcode: %u) from client %s, accountid=%u. Disconnected client.", - aptr->GetOpcode(), GetRemoteAddress().c_str(), m_Session ? m_Session->GetAccountId() : 0); - - if (sLog->ShouldLog("network", LogLevel::LOG_LEVEL_DEBUG)) - { - LOG_DEBUG("network", "Dumping error causing packet:"); - new_pct->hexlike(); - } - - return -1; + LOG_TRACE("network.opcode", "C->S: %s %s", (_worldSession ? _worldSession->GetPlayerInfo() : GetRemoteIpAddress().to_string()).c_str(), + GetOpcodeNameForLogging(opcode).c_str()); } +} - std::lock_guard<std::mutex> guard(m_SessionLock); - - OpcodeHandler const* handler = opcodeTable[opcode]; - if (!handler) - { - LOG_ERROR("network.opcode", "No defined handler for opcode %s sent by %s", GetOpcodeNameForLogging(static_cast<OpcodeClient>(aptr->GetOpcode())).c_str(), m_Session->GetPlayerInfo().c_str()); - return -1; - } +void WorldSocket::SendPacketAndLogOpcode(WorldPacket const& packet) +{ + LOG_TRACE("network.opcode", "S->C: %s %s", GetRemoteIpAddress().to_string().c_str(), GetOpcodeNameForLogging(static_cast<OpcodeServer>(packet.GetOpcode())).c_str()); + SendPacket(packet); +} - if (m_Session != nullptr) - { - // Our Idle timer will reset on any non PING or TIME_SYNC opcodes. - // Catches people idling on the login screen and any lingering ingame connections. - if (opcode != CMSG_PING && opcode != CMSG_TIME_SYNC_RESP) - { - m_Session->ResetTimeOutTime(false); - } +void WorldSocket::SendPacket(WorldPacket const& packet) +{ + if (!IsOpen()) + return; - // OK, give the packet to WorldSession - aptr.release(); - m_Session->QueuePacket(new_pct); - return 0; - } + if (sPacketLog->CanLogPacket()) + sPacketLog->LogPacket(packet, SERVER_TO_CLIENT, GetRemoteIpAddress(), GetRemotePort()); - LOG_ERROR("network", "WorldSocket::ProcessIncoming: Client not authed opcode = %u", aptr->GetOpcode()); - return -1; + _bufferQueue.Enqueue(new EncryptablePacket(packet, _authCrypt.IsInitialized())); } -int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) +void WorldSocket::HandleAuthSession(WorldPacket & recvPacket) { - // NOTE: ATM the socket is singlethread, have this in mind ... - uint32 loginServerID, loginServerType, regionID, battlegroupID, realmid; - uint64 DosResponse; - uint32 BuiltNumberClient; - std::string accountName; - WorldPacket packet, SendAddonPacked; - std::array<uint8, 4> clientSeed; - Acore::Crypto::SHA1::Digest digest; - - if (sWorld->IsClosed()) - { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_REJECT); - SendPacket(packet); - - LOG_ERROR("network", "WorldSocket::HandleAuthSession: World closed, denying client (%s).", GetRemoteAddress().c_str()); - return -1; - } + std::shared_ptr<AuthSession> authSession = std::make_shared<AuthSession>(); // Read the content of the packet - recvPacket >> BuiltNumberClient; // for now no use - recvPacket >> loginServerID; - recvPacket >> accountName; - recvPacket >> loginServerType; - recvPacket.read(clientSeed); - recvPacket >> regionID; - recvPacket >> battlegroupID; - recvPacket >> realmid; - recvPacket >> DosResponse; - recvPacket.read(digest); - - LOG_DEBUG("network", "WorldSocket::HandleAuthSession: client %u, loginServerID %u, accountName %s, loginServerType %u", - BuiltNumberClient, loginServerID, accountName.c_str(), loginServerType); - - // Get the account information from the realmd database - // 0 1 2 3 4 5 6 7 8 9 10 - // SELECT id, sessionkey, last_ip, locked, lock_country, expansion, mutetime, locale, recruiter, os, totaltime FROM account WHERE username = ? - auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME); + recvPacket >> authSession->Build; + recvPacket >> authSession->LoginServerID; + recvPacket >> authSession->Account; + recvPacket >> authSession->LoginServerType; + recvPacket.read(authSession->LocalChallenge); + recvPacket >> authSession->RegionID; + recvPacket >> authSession->BattlegroupID; + recvPacket >> authSession->RealmID; // realmId from auth_database.realmlist table + recvPacket >> authSession->DosResponse; + recvPacket.read(authSession->Digest); + authSession->AddonInfo.resize(recvPacket.size() - recvPacket.rpos()); + recvPacket.read(authSession->AddonInfo.contents(), authSession->AddonInfo.size()); // .contents will throw if empty, thats what we want + + // Get the account information from the auth database + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME); stmt->setInt32(0, int32(realm.Id.Realm)); - stmt->setString(1, accountName); + stmt->setString(1, authSession->Account); - PreparedQueryResult result = LoginDatabase.Query(stmt); + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSocket::HandleAuthSessionCallback, this, authSession, std::placeholders::_1))); +} +void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<AuthSession> authSession, PreparedQueryResult result) +{ // Stop if the account is not found if (!result) { // We can not log here, as we do not know the account. Thus, no accountId. - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_UNKNOWN_ACCOUNT); - - SendPacket(packet); - + SendAuthResponseError(AUTH_UNKNOWN_ACCOUNT); LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (unknown account)."); - return -1; + DelayedCloseSocket(); + return; } AccountInfo account(result->Fetch()); - // For hook purposes, we get Remoteaddress at this point - std::string address = GetRemoteAddress(); // Originally, this variable should be called address, but for some reason, that variable name was already on the core. + // For hook purposes, we get Remoteaddress at this point. + std::string address = GetRemoteIpAddress().to_string(); // As we don't know if attempted login process by ip works, we update last_attempt_ip right away - stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_ATTEMPT_IP); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_ATTEMPT_IP); stmt->setString(0, address); - stmt->setString(1, accountName); + stmt->setString(1, authSession->Account); LoginDatabase.Execute(stmt); // This also allows to check for possible "hack" attempts on account // even if auth credentials are bad, try using the session key we have - client cannot read auth response error without it - m_Crypt.Init(account.SessionKey); + _authCrypt.Init(account.SessionKey); // First reject the connection if packet contains invalid data or realm state doesn't allow logging in if (sWorld->IsClosed()) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_REJECT); - SendPacket(packet); - - LOG_ERROR("network", "WorldSocket::HandleAuthSession: World closed, denying client (%s).", address.c_str()); - sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + SendAuthResponseError(AUTH_REJECT); + LOG_ERROR("network", "WorldSocket::HandleAuthSession: World closed, denying client (%s).", GetRemoteIpAddress().to_string().c_str()); + DelayedCloseSocket(); + return; } - if (realmid != realm.Id.Realm) + if (authSession->RealmID != realm.Id.Realm) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(REALM_LIST_REALM_NOT_FOUND); - SendPacket(packet); - - LOG_ERROR("server", "WorldSocket::HandleAuthSession: Client %s requested connecting with realm id %u but this realm has id %u set in config.", - address.c_str(), realmid, realm.Id.Realm); - sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + SendAuthResponseError(REALM_LIST_REALM_NOT_FOUND); + LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client %s requested connecting with realm id %u but this realm has id %u set in config.", + GetRemoteIpAddress().to_string().c_str(), authSession->RealmID, realm.Id.Realm); + DelayedCloseSocket(); + return; } // Must be done before WorldSession is created bool wardenActive = sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED); if (wardenActive && account.OS != "Win" && account.OS != "OSX") { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_REJECT); - SendPacket(packet); + SendAuthResponseError(AUTH_REJECT); LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client %s attempted to log in using invalid client OS (%s).", address.c_str(), account.OS.c_str()); - sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + DelayedCloseSocket(); + return; } // Check that Key and account name are the same on client and server - uint8 t[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8 t[4] = { 0x00,0x00,0x00,0x00 }; Acore::Crypto::SHA1 sha; - sha.UpdateData(accountName); + sha.UpdateData(authSession->Account); sha.UpdateData(t); - sha.UpdateData(clientSeed); - sha.UpdateData(m_Seed); + sha.UpdateData(authSession->LocalChallenge); + sha.UpdateData(_authSeed); sha.UpdateData(account.SessionKey); sha.Finalize(); - if (sha.GetDigest() != digest) + if (sha.GetDigest() != authSession->Digest) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_FAILED); - SendPacket(packet); - LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: %u ('%s') address: %s", account.Id, accountName.c_str(), address.c_str()); - return -1; + SendAuthResponseError(AUTH_FAILED); + LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: %u ('%s') address: %s", account.Id, authSession->Account.c_str(), address.c_str()); + DelayedCloseSocket(); + return; } if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(address)) @@ -922,36 +519,34 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) { if (account.LastIP != address) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_FAILED); - SendPacket(packet); + SendAuthResponseError(AUTH_FAILED); LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: %s, new IP: %s).", account.LastIP.c_str(), address.c_str()); // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + DelayedCloseSocket(); + return; } } else if (!account.LockCountry.empty() && account.LockCountry != "00" && !_ipCountry.empty()) { if (account.LockCountry != _ipCountry) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_REJECT); + SendAuthResponseError(AUTH_FAILED); LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account country differs. Original country: %s, new country: %s).", account.LockCountry.c_str(), _ipCountry.c_str()); // We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + DelayedCloseSocket(); + return; } } if (account.IsBanned) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_BANNED); - SendPacket(packet); + SendAuthResponseError(AUTH_BANNED); LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account banned)."); sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + DelayedCloseSocket(); + return; } // Check locked state for server @@ -959,54 +554,55 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) LOG_DEBUG("network", "Allowed Level: %u Player Level %u", allowedAccountType, account.Security); if (allowedAccountType > SEC_PLAYER && account.Security < allowedAccountType) { - packet.Initialize(SMSG_AUTH_RESPONSE, 1); - packet << uint8(AUTH_UNAVAILABLE); + SendAuthResponseError(AUTH_UNAVAILABLE); LOG_DEBUG("network", "WorldSocket::HandleAuthSession: User tries to login but his security level is not enough"); sScriptMgr->OnFailedAccountLogin(account.Id); - return -1; + DelayedCloseSocket(); + return; } - LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '%s' authenticated successfully from %s.", accountName.c_str(), address.c_str()); + LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '%s' authenticated successfully from %s.", authSession->Account.c_str(), address.c_str()); // Update the last_ip in the database as it was successful for login stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP); stmt->setString(0, address); - stmt->setString(1, accountName); - LoginDatabase.Execute(stmt); + stmt->setString(1, authSession->Account); - sScriptMgr->OnLastIpUpdate(account.Id, address); + LoginDatabase.Execute(stmt); - bool skipQueue = false; + // At this point, we can safely hook a successful login + sScriptMgr->OnAccountLogin(account.Id); - if (account.Security > SEC_PLAYER) - skipQueue = true; + _authed = true; - // NOTE ATM the socket is single-threaded, have this in mind ... - ACE_NEW_RETURN(m_Session, - WorldSession(account.Id, this, AccountTypes(account.Security), account.Expansion, account.MuteTime, account.Locale, account.Recruiter, account.IsRectuiter, skipQueue, account.TotalTime), -1); + _worldSession = new WorldSession(account.Id, std::move(authSession->Account), shared_from_this(), account.Security, + account.Expansion, account.MuteTime, account.Locale, account.Recruiter, account.IsRectuiter, account.Security ? true : false, account.TotalTime); - m_Session->LoadGlobalAccountData(); - m_Session->LoadTutorialsData(); - m_Session->ReadAddonsInfo(recvPacket); - - // At this point, we can safely hook a successful login - sScriptMgr->OnAccountLogin(account.Id); + _worldSession->ReadAddonsInfo(authSession->AddonInfo); // Initialize Warden system only if it is enabled by config if (wardenActive) - m_Session->InitWarden(account.SessionKey, account.OS); + { + _worldSession->InitWarden(account.SessionKey, account.OS); + } - // Sleep this Network thread for - uint32 sleepTime = sWorld->getIntConfig(CONFIG_SESSION_ADD_DELAY); - std::this_thread::sleep_for(Microseconds(sleepTime)); + sWorld->AddSession(_worldSession); + + AsyncRead(); +} - sWorld->AddSession(m_Session); +void WorldSocket::SendAuthResponseError(uint8 code) +{ + WorldPacket packet(SMSG_AUTH_RESPONSE, 1); + packet << uint8(code); - return 0; + SendPacketAndLogOpcode(packet); } -int WorldSocket::HandlePing(WorldPacket& recvPacket) +bool WorldSocket::HandlePing(WorldPacket& recvPacket) { + using namespace std::chrono; + uint32 ping; uint32 latency; @@ -1014,60 +610,57 @@ int WorldSocket::HandlePing(WorldPacket& recvPacket) recvPacket >> ping; recvPacket >> latency; - if (m_LastPingTime == SystemTimePoint::min()) - m_LastPingTime = std::chrono::system_clock::now(); // for 1st ping + if (_LastPingTime == steady_clock::time_point()) + { + _LastPingTime = steady_clock::now(); + } else { - auto now = std::chrono::system_clock::now(); - Seconds seconds = std::chrono::duration_cast<Seconds>(now - m_LastPingTime); - m_LastPingTime = now; + steady_clock::time_point now = steady_clock::now(); + steady_clock::duration diff = now - _LastPingTime; + + _LastPingTime = now; - if (seconds.count() < 27) + if (diff < seconds(27)) { - ++m_OverSpeedPings; + ++_OverSpeedPings; - uint32 max_count = sWorld->getIntConfig(CONFIG_MAX_OVERSPEED_PINGS); + uint32 maxAllowed = sWorld->getIntConfig(CONFIG_MAX_OVERSPEED_PINGS); - if (max_count && m_OverSpeedPings > max_count) + if (maxAllowed && _OverSpeedPings > maxAllowed) { - std::lock_guard<std::mutex> guard(m_SessionLock); + std::unique_lock<std::mutex> sessionGuard(_worldSessionLock); - if (m_Session && AccountMgr::IsPlayerAccount(m_Session->GetSecurity())) + if (_worldSession && AccountMgr::IsPlayerAccount(_worldSession->GetSecurity())) { - Player* _player = m_Session->GetPlayer(); - LOG_ERROR("network", "WorldSocket::HandlePing: Player (account: %u, %s, name: %s) kicked for over-speed pings (address: %s)", - m_Session->GetAccountId(), - _player ? _player->GetGUID().ToString().c_str() : "", - _player ? _player->GetName().c_str() : "<none>", - GetRemoteAddress().c_str()); - - return -1; + LOG_ERROR("network", "WorldSocket::HandlePing: %s kicked for over-speed pings (address: %s)", + _worldSession->GetPlayerInfo().c_str(), GetRemoteIpAddress().to_string().c_str()); + + return false; } } } else - m_OverSpeedPings = 0; + { + _OverSpeedPings = 0; + } } - // critical section { - std::lock_guard<std::mutex> guard(m_SessionLock); + std::lock_guard<std::mutex> sessionGuard(_worldSessionLock); - if (m_Session) - { - m_Session->SetLatency (latency); - } + if (_worldSession) + _worldSession->SetLatency(latency); else { - LOG_ERROR("network", "WorldSocket::HandlePing: peer sent CMSG_PING, " - "but is not authenticated or got recently kicked, " - " address = %s", - GetRemoteAddress().c_str()); - return -1; + LOG_ERROR("network", "WorldSocket::HandlePing: peer sent CMSG_PING, but is not authenticated or got recently kicked, address = %s", GetRemoteIpAddress().to_string().c_str()); + return false; } } - WorldPacket packet (SMSG_PONG, 4); + WorldPacket packet(SMSG_PONG, 4); packet << ping; - return SendPacket(packet); + SendPacketAndLogOpcode(packet); + + return true; } diff --git a/src/server/game/Server/WorldSocket.h b/src/server/game/Server/WorldSocket.h index 3bd34e2bd1..ab07efa957 100644 --- a/src/server/game/Server/WorldSocket.h +++ b/src/server/game/Server/WorldSocket.h @@ -4,197 +4,121 @@ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ -/** \addtogroup u2w User to World Communication - * @{ - * \file WorldSocket.h - * \author Derex <derex101@gmail.com> - */ - -#ifndef _WORLDSOCKET_H -#define _WORLDSOCKET_H +#ifndef __WORLDSOCKET_H__ +#define __WORLDSOCKET_H__ -#include "AuthCrypt.h" #include "Common.h" -#include "Duration.h" -#include <ace/Message_Block.h> -#include <ace/SOCK_Stream.h> -#include <ace/Svc_Handler.h> -#include <ace/Synch_Traits.h> -#include <ace/Unbounded_Queue.h> -#include <mutex> - -#if !defined (ACE_LACKS_PRAGMA_ONCE) -#pragma once -#endif /* ACE_LACKS_PRAGMA_ONCE */ - -class ACE_Message_Block; -class WorldPacket; -class WorldSession; +#include "AuthCrypt.h" +#include "ServerPktHeader.h" +#include "Socket.h" +#include "Util.h" +#include "WorldPacket.h" +#include "WorldSession.h" +#include "MPSCQueue.h" +#include <boost/asio/ip/tcp.hpp> -namespace WorldPackets -{ - class ServerPacket; -} +using boost::asio::ip::tcp; -/// Handler that can communicate over stream sockets. -typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> WorldHandler; - -/** - * WorldSocket. - * - * This class is responsible for the communication with - * remote clients. - * Most methods return -1 on failure. - * The class uses reference counting. - * - * For output the class uses one buffer (64K usually) and - * a queue where it stores packet if there is no place on - * the queue. The reason this is done, is because the server - * does really a lot of small-size writes to it, and it doesn't - * scale well to allocate memory for every. When something is - * written to the output buffer the socket is not immediately - * activated for output (again for the same reason), there - * is 10ms celling (thats why there is Update() method). - * This concept is similar to TCP_CORK, but TCP_CORK - * uses 200ms celling. As result overhead generated by - * sending packets from "producer" threads is minimal, - * and doing a lot of writes with small size is tolerated. - * - * The calls to Update() method are managed by WorldSocketMgr - * and ReactorRunnable. - * - * For input, the class uses one 4096 bytes buffer on stack - * to which it does recv() calls. And then received data is - * distributed where its needed. 4096 matches pretty well the - * traffic generated by client for now. - * - * The input/output do speculative reads/writes (AKA it tryes - * to read all data available in the kernel buffer or tryes to - * write everything available in userspace buffer), - * which is ok for using with Level and Edge Triggered IO - * notification. - * - */ -class WorldSocket : public WorldHandler +class EncryptablePacket : public WorldPacket { public: - WorldSocket (void); - virtual ~WorldSocket (void); - - friend class WorldSocketMgr; - - /// Check if socket is closed. - bool IsClosed (void) const; - - /// Close the socket. - void CloseSocket(std::string const& reason); + EncryptablePacket(WorldPacket const& packet, bool encrypt) : WorldPacket(packet), _encrypt(encrypt) + { + SocketQueueLink.store(nullptr, std::memory_order_relaxed); + } - /// Get address of connected peer. - const std::string& GetRemoteAddress (void) const; + bool NeedsEncryption() const { return _encrypt; } - /// Send A packet on the socket, this function is reentrant. - /// @param pct packet to send - /// @return -1 of failure - int SendPacket(const WorldPacket& pct); + std::atomic<EncryptablePacket*> SocketQueueLink; - /// Add reference to this object. - long AddReference (void); - - /// Remove reference to this object. - long RemoveReference (void); +private: + bool _encrypt; +}; - /// things called by ACE framework. +namespace WorldPackets +{ + class ServerPacket; +} - /// Called on open, the void* is the acceptor. - virtual int open (void*); +#pragma pack(push, 1) +struct ClientPktHeader +{ + uint16 size; + uint32 cmd; - /// Called on failures inside of the acceptor, don't call from your code. - virtual int close (u_long); + bool IsValidSize() const { return size >= 4 && size < 10240; } + bool IsValidOpcode() const { return cmd < NUM_OPCODE_HANDLERS; } +}; +#pragma pack(pop) - /// Called when we can read from the socket. - virtual int handle_input (ACE_HANDLE = ACE_INVALID_HANDLE); +struct AuthSession; - /// Called when the socket can write. - virtual int handle_output (ACE_HANDLE = ACE_INVALID_HANDLE); +class AC_GAME_API WorldSocket : public Socket<WorldSocket> +{ + typedef Socket<WorldSocket> BaseSocket; - /// Called when connection is closed or error happens. - virtual int handle_close (ACE_HANDLE = ACE_INVALID_HANDLE, - ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK); +public: + WorldSocket(tcp::socket&& socket); + ~WorldSocket(); - /// Called by WorldSocketMgr/ReactorRunnable. - int Update (void); + WorldSocket(WorldSocket const& right) = delete; + WorldSocket& operator=(WorldSocket const& right) = delete; -private: - /// Helper functions for processing incoming data. - int handle_input_header (void); - int handle_input_payload (void); - int handle_input_missing_data (void); + void Start() override; + bool Update() override; - /// Help functions to mark/unmark the socket for output. - /// @param g the guard is for m_OutBufferLock, the function will release it - int cancel_wakeup_output(); - int schedule_wakeup_output(); + void SendPacket(WorldPacket const& packet); - /// Drain the queue if its not empty. - int handle_output_queue(); + void SetSendBufferSize(std::size_t sendBufferSize) { _sendBufferSize = sendBufferSize; } - /// process one incoming packet. - /// @param new_pct received packet, note that you need to delete it. - int ProcessIncoming (WorldPacket* new_pct); +protected: + void OnClose() override; + void ReadHandler() override; + bool ReadHeaderHandler(); - /// Called by ProcessIncoming() on CMSG_AUTH_SESSION. - int HandleAuthSession (WorldPacket& recvPacket); + enum class ReadDataHandlerResult + { + Ok = 0, + Error = 1, + WaitingForQuery = 2 + }; - /// Called by ProcessIncoming() on CMSG_PING. - int HandlePing (WorldPacket& recvPacket); + ReadDataHandlerResult ReadDataHandler(); private: - /// Time in which the last ping was received - SystemTimePoint m_LastPingTime; + void CheckIpCallback(PreparedQueryResult result); - /// Keep track of over-speed pings, to prevent ping flood. - uint32 m_OverSpeedPings; + /// writes network.opcode log + /// accessing WorldSession is not threadsafe, only do it when holding _worldSessionLock + void LogOpcodeText(OpcodeClient opcode, std::unique_lock<std::mutex> const& guard) const; - /// Address of the remote peer - std::string m_Address; + /// sends and logs network.opcode without accessing WorldSession + void SendPacketAndLogOpcode(WorldPacket const& packet); + void HandleSendAuthSession(); + void HandleAuthSession(WorldPacket& recvPacket); + void HandleAuthSessionCallback(std::shared_ptr<AuthSession> authSession, PreparedQueryResult result); + void LoadSessionPermissionsCallback(PreparedQueryResult result); + void SendAuthResponseError(uint8 code); - /// Class used for managing encryption of the headers - AuthCrypt m_Crypt; + bool HandlePing(WorldPacket& recvPacket); - /// Mutex lock to protect m_Session - std::mutex m_SessionLock; + std::array<uint8, 4> _authSeed; + AuthCrypt _authCrypt; - /// Session to which received packets are routed - WorldSession* m_Session; + TimePoint _LastPingTime; + uint32 _OverSpeedPings; - /// here are stored the fragments of the received data - WorldPacket* m_RecvWPct; + std::mutex _worldSessionLock; + WorldSession* _worldSession; + bool _authed; - /// This block actually refers to m_RecvWPct contents, - /// which allows easy and safe writing to it. - /// It wont free memory when its deleted. m_RecvWPct takes care of freeing. - ACE_Message_Block m_RecvPct; - - /// Fragment of the received header. - ACE_Message_Block m_Header; - - /// Mutex for protecting output related data. - std::mutex m_OutBufferLock; - - /// Buffer used for writing output. - ACE_Message_Block* m_OutBuffer; - - /// Size of the m_OutBuffer. - size_t m_OutBufferSize; - - /// True if the socket is registered with the reactor for output - bool m_OutActive; - - std::array<uint8, 4> m_Seed; + MessageBuffer _headerBuffer; + MessageBuffer _packetBuffer; + MPSCQueue<EncryptablePacket, &EncryptablePacket::SocketQueueLink> _bufferQueue; + std::size_t _sendBufferSize; + QueryCallbackProcessor _queryProcessor; std::string _ipCountry; }; -#endif /* _WORLDSOCKET_H */ - -/// @} +#endif diff --git a/src/server/game/Server/WorldSocketAcceptor.h b/src/server/game/Server/WorldSocketAcceptor.h deleted file mode 100644 index 1b058ca36b..0000000000 --- a/src/server/game/Server/WorldSocketAcceptor.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -/** \addtogroup u2w User to World Communication - * @{ - * \file WorldSocketMgr.h - */ - -#ifndef __WORLDSOCKETACCEPTOR_H_ -#define __WORLDSOCKETACCEPTOR_H_ - -#include "Common.h" -#include "WorldSocket.h" -#include <ace/Acceptor.h> -#include <ace/SOCK_Acceptor.h> - -class WorldSocketAcceptor : public ACE_Acceptor<WorldSocket, ACE_SOCK_Acceptor> -{ -public: - WorldSocketAcceptor(void) { } - virtual ~WorldSocketAcceptor(void) - { - if (reactor()) - reactor()->cancel_timer(this, 1); - } - -protected: - virtual int handle_timeout(const ACE_Time_Value& /*current_time*/, const void* /*act = 0*/) - { - LOG_INFO("network", "Resuming acceptor"); - reactor()->cancel_timer(this, 1); - return reactor()->register_handler(this, ACE_Event_Handler::ACCEPT_MASK); - } - - virtual int handle_accept_error(void) - { -#if defined(ENFILE) && defined(EMFILE) - if (errno == ENFILE || errno == EMFILE) - { - LOG_ERROR("network", "Out of file descriptors, suspending incoming connections for 10 seconds"); - reactor()->remove_handler(this, ACE_Event_Handler::ACCEPT_MASK | ACE_Event_Handler::DONT_CALL); - reactor()->schedule_timer(this, nullptr, ACE_Time_Value(10)); - } -#endif - return 0; - } -}; - -#endif /* __WORLDSOCKETACCEPTOR_H_ */ -/// @} diff --git a/src/server/game/Server/WorldSocketMgr.cpp b/src/server/game/Server/WorldSocketMgr.cpp index 138ffbf30a..48f71f079e 100644 --- a/src/server/game/Server/WorldSocketMgr.cpp +++ b/src/server/game/Server/WorldSocketMgr.cpp @@ -4,345 +4,109 @@ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ -/** \file WorldSocketMgr.cpp -* \ingroup u2w -* \author Derex <derex101@gmail.com> -*/ - #include "Config.h" -#include "DatabaseEnv.h" -#include "Log.h" +#include "NetworkThread.h" #include "ScriptMgr.h" #include "WorldSocket.h" -#include "WorldSocketAcceptor.h" #include "WorldSocketMgr.h" -#include <ace/ACE.h> -#include <ace/Dev_Poll_Reactor.h> -#include <ace/Log_Msg.h> -#include <ace/os_include/arpa/os_inet.h> -#include <ace/os_include/netinet/os_tcp.h> -#include <ace/os_include/sys/os_socket.h> -#include <ace/Reactor_Impl.h> -#include <ace/Reactor.h> -#include <ace/TP_Reactor.h> -#include <atomic> -#include <set> +#include <boost/system/error_code.hpp> -/** -* This is a helper class to WorldSocketMgr, that manages -* network threads, and assigning connections from acceptor thread -* to other network threads -*/ -class ReactorRunnable : protected ACE_Task_Base +static void OnSocketAccept(tcp::socket&& sock, uint32 threadIndex) { -public: - ReactorRunnable() : - m_Reactor(0), - m_Connections(0), - m_ThreadId(-1) - { - ACE_Reactor_Impl* imp; - -#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL) - - imp = new ACE_Dev_Poll_Reactor(); - - imp->max_notify_iterations (128); - imp->restart (1); - -#else - - imp = new ACE_TP_Reactor(); - imp->max_notify_iterations (128); - -#endif - - m_Reactor = new ACE_Reactor (imp, 1); - } - - ~ReactorRunnable() override - { - Stop(); - Wait(); - - delete m_Reactor; - } - - void Stop() - { - m_Reactor->end_reactor_event_loop(); - } - - int Start() - { - if (m_ThreadId != -1) - return -1; - - return (m_ThreadId = activate()); - } - - void Wait() { ACE_Task_Base::wait(); } - - long Connections() - { - return m_Connections; - } + sWorldSocketMgr.OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex); +} - int AddSocket (WorldSocket* sock) +class WorldSocketThread : public NetworkThread<WorldSocket> +{ +public: + void SocketAdded(std::shared_ptr<WorldSocket> sock) override { - std::lock_guard<std::mutex> guard(m_NewSockets_Lock); - - ++m_Connections; - sock->AddReference(); - sock->reactor (m_Reactor); - m_NewSockets.insert (sock); - + sock->SetSendBufferSize(sWorldSocketMgr.GetApplicationSendBufferSize()); sScriptMgr->OnSocketOpen(sock); - - return 0; - } - - ACE_Reactor* GetReactor() - { - return m_Reactor; - } - -protected: - void AddNewSockets() - { - std::lock_guard<std::mutex> guard(m_NewSockets_Lock); - - if (m_NewSockets.empty()) - return; - - for (SocketSet::const_iterator i = m_NewSockets.begin(); i != m_NewSockets.end(); ++i) - { - WorldSocket* sock = (*i); - - if (sock->IsClosed()) - { - sScriptMgr->OnSocketClose(sock, true); - - sock->RemoveReference(); - --m_Connections; - } - else - m_Sockets.insert (sock); - } - - m_NewSockets.clear(); } - int svc() override + void SocketRemoved(std::shared_ptr<WorldSocket> sock) override { - LOG_DEBUG("network", "Network Thread Starting"); - - ASSERT(m_Reactor); - - SocketSet::iterator i, t; - - while (!m_Reactor->reactor_event_loop_done()) - { - // dont be too smart to move this outside the loop - // the run_reactor_event_loop will modify interval - ACE_Time_Value interval (0, 10000); - - if (m_Reactor->run_reactor_event_loop (interval) == -1) - break; - - AddNewSockets(); - - for (i = m_Sockets.begin(); i != m_Sockets.end();) - { - if ((*i)->Update() == -1) - { - t = i; - ++i; - - (*t)->CloseSocket("svc()"); - - sScriptMgr->OnSocketClose((*t), false); - - (*t)->RemoveReference(); - --m_Connections; - m_Sockets.erase (t); - } - else - ++i; - } - } - - LOG_DEBUG("network", "Network Thread exits"); - - return 0; + sScriptMgr->OnSocketClose(sock); } - -private: - typedef std::atomic<int> AtomicInt; - typedef std::set<WorldSocket*> SocketSet; - - ACE_Reactor* m_Reactor; - AtomicInt m_Connections; - int m_ThreadId; - - SocketSet m_Sockets; - - SocketSet m_NewSockets; - std::mutex m_NewSockets_Lock; }; WorldSocketMgr::WorldSocketMgr() : - m_NetThreads(0), - m_NetThreadsCount(0), - m_SockOutKBuff(-1), - m_SockOutUBuff(65536), - m_UseNoDelay(true), - m_Acceptor (0) -{ -} - -WorldSocketMgr::~WorldSocketMgr() + BaseSocketMgr(), _socketSystemSendBufferSize(-1), _socketApplicationSendBufferSize(65536), _tcpNoDelay(true) { - delete [] m_NetThreads; - delete m_Acceptor; } -WorldSocketMgr* WorldSocketMgr::instance() +WorldSocketMgr& WorldSocketMgr::Instance() { static WorldSocketMgr instance; - return &instance; + return instance; } -int -WorldSocketMgr::StartReactiveIO (uint16 port, const char* address) +bool WorldSocketMgr::StartWorldNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount) { - m_UseNoDelay = sConfigMgr->GetOption<bool> ("Network.TcpNodelay", true); - - int num_threads = sConfigMgr->GetOption<int32> ("Network.Threads", 1); - - if (num_threads <= 0) - { - LOG_ERROR("network", "Network.Threads is wrong in your config file"); - return -1; - } - - m_NetThreadsCount = static_cast<size_t> (num_threads + 1); - - m_NetThreads = new ReactorRunnable[m_NetThreadsCount]; + _tcpNoDelay = sConfigMgr->GetOption<bool>("Network.TcpNodelay", true); - LOG_INFO("network", "Max allowed socket connections %d", ACE::max_handles()); + int const max_connections = ACORE_MAX_LISTEN_CONNECTIONS; + LOG_DEBUG("network", "Max allowed socket connections %d", max_connections); // -1 means use default - m_SockOutKBuff = sConfigMgr->GetOption<int32> ("Network.OutKBuff", -1); + _socketSystemSendBufferSize = sConfigMgr->GetOption<int32>("Network.OutKBuff", -1); + _socketApplicationSendBufferSize = sConfigMgr->GetOption<int32>("Network.OutUBuff", 65536); - m_SockOutUBuff = sConfigMgr->GetOption<int32> ("Network.OutUBuff", 65536); - - if (m_SockOutUBuff <= 0) + if (_socketApplicationSendBufferSize <= 0) { LOG_ERROR("network", "Network.OutUBuff is wrong in your config file"); - return -1; - } - - m_Acceptor = new WorldSocketAcceptor; - - ACE_INET_Addr listen_addr (port, address); - - if (m_Acceptor->open(listen_addr, m_NetThreads[0].GetReactor(), ACE_NONBLOCK) == -1) - { - LOG_ERROR("network", "Failed to open acceptor, check if the port is free"); - return -1; + return false; } - for (size_t i = 0; i < m_NetThreadsCount; ++i) - m_NetThreads[i].Start(); + if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount)) + return false; - return 0; -} - -int -WorldSocketMgr::StartNetwork (uint16 port, const char* address) -{ - if (!sLog->ShouldLog("network", LogLevel::LOG_LEVEL_DEBUG)) - ACE_Log_Msg::instance()->priority_mask(LM_ERROR, ACE_Log_Msg::PROCESS); - - if (StartReactiveIO(port, address) == -1) - return -1; + _acceptor->AsyncAcceptWithCallback<&OnSocketAccept>(); sScriptMgr->OnNetworkStart(); - - return 0; + return true; } -void -WorldSocketMgr::StopNetwork() +void WorldSocketMgr::StopNetwork() { - if (m_Acceptor) - { - m_Acceptor->close(); - } - - if (m_NetThreadsCount != 0) - { - for (size_t i = 0; i < m_NetThreadsCount; ++i) - m_NetThreads[i].Stop(); - } - - Wait(); + BaseSocketMgr::StopNetwork(); sScriptMgr->OnNetworkStop(); } -void -WorldSocketMgr::Wait() -{ - if (m_NetThreadsCount != 0) - { - for (size_t i = 0; i < m_NetThreadsCount; ++i) - m_NetThreads[i].Wait(); - } -} - -int -WorldSocketMgr::OnSocketOpen (WorldSocket* sock) +void WorldSocketMgr::OnSocketOpen(tcp::socket&& sock, uint32 threadIndex) { // set some options here - if (m_SockOutKBuff >= 0) + if (_socketSystemSendBufferSize >= 0) { - if (sock->peer().set_option (SOL_SOCKET, - SO_SNDBUF, - (void*) & m_SockOutKBuff, - sizeof (int)) == -1 && errno != ENOTSUP) + boost::system::error_code err; + sock.set_option(boost::asio::socket_base::send_buffer_size(_socketSystemSendBufferSize), err); + + if (err && err != boost::system::errc::not_supported) { - LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen set_option SO_SNDBUF"); - return -1; + LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen sock.set_option(boost::asio::socket_base::send_buffer_size) err = %s", err.message().c_str()); + return; } } - static const int ndoption = 1; - // Set TCP_NODELAY. - if (m_UseNoDelay) + if (_tcpNoDelay) { - if (sock->peer().set_option (ACE_IPPROTO_TCP, - TCP_NODELAY, - (void*)&ndoption, - sizeof (int)) == -1) + boost::system::error_code err; + sock.set_option(boost::asio::ip::tcp::no_delay(true), err); + + if (err) { - LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen: peer().set_option TCP_NODELAY errno = %s", ACE_OS::strerror (errno)); - return -1; + LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen sock.set_option(boost::asio::ip::tcp::no_delay) err = %s", err.message().c_str()); + return; } } - sock->m_OutBufferSize = static_cast<size_t> (m_SockOutUBuff); - - // we skip the Acceptor Thread - size_t min = 1; - - ASSERT(m_NetThreadsCount >= 1); - - for (size_t i = 1; i < m_NetThreadsCount; ++i) - if (m_NetThreads[i].Connections() < m_NetThreads[min].Connections()) - min = i; + BaseSocketMgr::OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex); +} - return m_NetThreads[min].AddSocket (sock); +NetworkThread<WorldSocket>* WorldSocketMgr::CreateThreads() const +{ + return new WorldSocketThread[GetNetworkThreadCount()]; } diff --git a/src/server/game/Server/WorldSocketMgr.h b/src/server/game/Server/WorldSocketMgr.h index a46583d4bb..c2ae6e8fe2 100644 --- a/src/server/game/Server/WorldSocketMgr.h +++ b/src/server/game/Server/WorldSocketMgr.h @@ -4,58 +4,43 @@ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ -/** \addtogroup u2w User to World Communication - * @{ - * \file WorldSocketMgr.h - * \author Derex <derex101@gmail.com> - */ - #ifndef __WORLDSOCKETMGR_H #define __WORLDSOCKETMGR_H -#include "Common.h" +#include "SocketMgr.h" class WorldSocket; -class ReactorRunnable; -class ACE_Event_Handler; /// Manages all sockets connected to peers and network threads -class WorldSocketMgr +class AC_GAME_API WorldSocketMgr : public SocketMgr<WorldSocket> { -public: - friend class WorldSocket; + typedef SocketMgr<WorldSocket> BaseSocketMgr; - static WorldSocketMgr* instance(); +public: + static WorldSocketMgr& Instance(); /// Start network, listen at address:port . - int StartNetwork(uint16 port, const char* address); + bool StartWorldNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int networkThreads); /// Stops all network threads, It will wait for all running threads . - void StopNetwork(); + void StopNetwork() override; - /// Wait untill all network threads have "joined" . - void Wait(); + void OnSocketOpen(tcp::socket&& sock, uint32 threadIndex) override; -private: - int OnSocketOpen(WorldSocket* sock); + std::size_t GetApplicationSendBufferSize() const { return _socketApplicationSendBufferSize; } - int StartReactiveIO(uint16 port, const char* address); - -private: +protected: WorldSocketMgr(); - virtual ~WorldSocketMgr(); - ReactorRunnable* m_NetThreads; - size_t m_NetThreadsCount; + NetworkThread<WorldSocket>* CreateThreads() const override; - int m_SockOutKBuff; - int m_SockOutUBuff; - bool m_UseNoDelay; - - class WorldSocketAcceptor* m_Acceptor; +private: + int32 _socketSystemSendBufferSize; + int32 _socketApplicationSendBufferSize; + bool _tcpNoDelay; }; -#define sWorldSocketMgr WorldSocketMgr::instance() +#define sWorldSocketMgr WorldSocketMgr::Instance() #endif /// @} diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 2e3edd63eb..0152f8f548 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -8,6 +8,7 @@ \ingroup world */ +#include "World.h" #include "AccountMgr.h" #include "AchievementMgr.h" #include "AddonMgr.h" @@ -28,8 +29,8 @@ #include "CreatureAIRegistry.h" #include "CreatureGroups.h" #include "CreatureTextMgr.h" -#include "DatabaseEnv.h" #include "DBCStores.h" +#include "DatabaseEnv.h" #include "DisableMgr.h" #include "DynamicVisibility.h" #include "GameEventMgr.h" @@ -38,16 +39,16 @@ #include "GridNotifiersImpl.h" #include "GroupMgr.h" #include "GuildMgr.h" +#include "IPLocation.h" #include "InstanceSaveMgr.h" #include "ItemEnchantmentMgr.h" -#include "IPLocation.h" -#include "Language.h" #include "LFGMgr.h" +#include "Language.h" #include "Log.h" #include "LootItemStorage.h" #include "LootMgr.h" -#include "MapManager.h" #include "MMapFactory.h" +#include "MapManager.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "OutdoorPvPMgr.h" @@ -66,17 +67,18 @@ #include "Transport.h" #include "TransportMgr.h" #include "Util.h" -#include "Vehicle.h" #include "VMapFactory.h" +#include "VMapManager2.h" +#include "Vehicle.h" #include "Warden.h" #include "WardenCheckMgr.h" #include "WaypointMovementGenerator.h" #include "WeatherMgr.h" #include "WhoListCache.h" -#include "World.h" #include "WorldPacket.h" #include "WorldSession.h" -#include <VMapManager2.h> +#include <boost/asio/ip/address.hpp> +#include <cmath> #ifdef ELUNA #include "LuaEngine.h" diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 1561c9fa81..c4099745f8 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -5,7 +5,6 @@ */ #include "AccountMgr.h" -#include "ace/INET_Addr.h" #include "ArenaTeamMgr.h" #include "BattlegroundMgr.h" #include "CellImpl.h" diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp index 9a31c92ade..441207b780 100644 --- a/src/server/scripts/Commands/cs_server.cpp +++ b/src/server/scripts/Commands/cs_server.cpp @@ -117,7 +117,6 @@ public: handler->PSendSysMessage("%s", GitRevision::GetFullVersion()); handler->PSendSysMessage("Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); - handler->PSendSysMessage("Using ACE version: %s", ACE_VERSION); handler->PSendSysMessage("Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); handler->PSendSysMessage("Using MySQL version: %u", MySQL::GetLibraryVersion()); handler->PSendSysMessage("Using CMake version: %s", GitRevision::GetCMakeVersion()); diff --git a/src/server/scripts/Commands/cs_spectator.cpp b/src/server/scripts/Commands/cs_spectator.cpp index 980f421bc0..b93bc16258 100644 --- a/src/server/scripts/Commands/cs_spectator.cpp +++ b/src/server/scripts/Commands/cs_spectator.cpp @@ -21,8 +21,8 @@ public: { { "version", SEC_CONSOLE, false, &HandleSpectatorVersionCommand, "" }, { "reset", SEC_CONSOLE, false, &HandleSpectatorResetCommand, "" }, - { "spectate", SEC_CONSOLE, false, &ArenaSpectator::HandleSpectatorSpectateCommand, "" }, - { "watch", SEC_CONSOLE, false, &ArenaSpectator::HandleSpectatorWatchCommand, "" }, + { "spectate", SEC_CONSOLE, false, &HandleSpectatorSpectateCommand, "" }, + { "watch", SEC_CONSOLE, false, &HandleSpectatorWatchCommand, "" }, { "leave", SEC_CONSOLE, false, &HandleSpectatorLeaveCommand, "" }, { "", SEC_CONSOLE, false, &HandleSpectatorCommand, "" } }; @@ -71,6 +71,22 @@ public: player->TeleportToEntryPoint(); return true; } + + static bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args) + { + if (!ArenaSpectator::HandleSpectatorSpectateCommand(handler, args)) + return false; + + return true; + } + + static bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args) + { + if (!ArenaSpectator::HandleSpectatorWatchCommand(handler, args)) + return false; + + return true; + } }; void AddSC_spectator_commandscript() diff --git a/src/server/shared/Network/AsyncAcceptor.h b/src/server/shared/Network/AsyncAcceptor.h index 5b218bf227..64f52d1808 100644 --- a/src/server/shared/Network/AsyncAcceptor.h +++ b/src/server/shared/Network/AsyncAcceptor.h @@ -16,9 +16,9 @@ using boost::asio::ip::tcp; #if BOOST_VERSION >= 106600 -#define WARHEAD_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_listen_connections +#define ACORE_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_listen_connections #else -#define WARHEAD_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_connections +#define ACORE_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_connections #endif class AsyncAcceptor @@ -72,7 +72,7 @@ public: return false; } -#if WARHEAD_PLATFORM != WARHEAD_PLATFORM_WINDOWS +#if AC_PLATFORM != AC_PLATFORM_WINDOWS _acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode); if (errorCode) { @@ -88,7 +88,7 @@ public: return false; } - _acceptor.listen(WARHEAD_MAX_LISTEN_CONNECTIONS, errorCode); + _acceptor.listen(ACORE_MAX_LISTEN_CONNECTIONS, errorCode); if (errorCode) { LOG_INFO("network", "Failed to start listening on %s:%u %s", _endpoint.address().to_string().c_str(), _endpoint.port(), errorCode.message().c_str()); diff --git a/src/server/shared/Network/RealmSocket.cpp b/src/server/shared/Network/RealmSocket.cpp deleted file mode 100644 index ec44dbbf6f..0000000000 --- a/src/server/shared/Network/RealmSocket.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -#include "Log.h" -#include "RealmSocket.h" -#include <ace/INET_Addr.h> -#include <ace/OS_NS_string.h> - -RealmSocket::Session::Session() = default; - -RealmSocket::Session::~Session() = default; - -RealmSocket::RealmSocket() : - input_buffer_(4096), - _remoteAddress() -{ - reference_counting_policy().value(ACE_Event_Handler::Reference_Counting_Policy::ENABLED); - - msg_queue()->high_water_mark(8 * 1024 * 1024); - msg_queue()->low_water_mark(8 * 1024 * 1024); -} - -RealmSocket::~RealmSocket() -{ - if (msg_queue()) - msg_queue()->close(); - - // delete RealmSocketObject must never be called from our code. - closing_ = true; - - delete session_; - - peer().close(); -} - -int RealmSocket::open(void* arg) -{ - ACE_INET_Addr addr; - - if (peer().get_remote_addr(addr) == -1) - { - LOG_ERROR("network", "Error %s while opening realm socket!", ACE_OS::strerror(errno)); - return -1; - } - - _remoteAddress = addr.get_host_addr(); - _remotePort = addr.get_port_number(); - - // Register with ACE Reactor - if (Base::open(arg) == -1) - return -1; - - if (session_) - session_->OnAccept(); - - // reactor takes care of the socket from now on - remove_reference(); - - return 0; -} - -int RealmSocket::close(u_long) -{ - shutdown(); - - closing_ = true; - - remove_reference(); - - return 0; -} - -const std::string& RealmSocket::getRemoteAddress() const -{ - return _remoteAddress; -} - -uint16 RealmSocket::getRemotePort() const -{ - return _remotePort; -} - -size_t RealmSocket::recv_len() const -{ - return input_buffer_.length(); -} - -bool RealmSocket::recv_soft(char* buf, size_t len) -{ - if (input_buffer_.length() < len) - return false; - - ACE_OS::memcpy(buf, input_buffer_.rd_ptr(), len); - - return true; -} - -bool RealmSocket::recv(char* buf, size_t len) -{ - bool ret = recv_soft(buf, len); - - if (ret) - recv_skip(len); - - return ret; -} - -void RealmSocket::recv_skip(size_t len) -{ - input_buffer_.rd_ptr(len); -} - -ssize_t RealmSocket::noblk_send(ACE_Message_Block& message_block) -{ - const size_t len = message_block.length(); - - if (len == 0) - return -1; - - // Try to send the message directly. -#ifdef MSG_NOSIGNAL - ssize_t n = peer().send(message_block.rd_ptr(), len, MSG_NOSIGNAL); -#else - ssize_t n = peer().send(message_block.rd_ptr(), len); -#endif // MSG_NOSIGNAL - - if (n < 0) - { - if (errno == EWOULDBLOCK) // Blocking signal - return 0; - else // Error happened - return -1; - } - else if (n == 0) - { - // Can this happen ? - return -1; - } - - // return bytes transmitted - return n; -} - -bool RealmSocket::send(const char* buf, size_t len) -{ - if (buf == nullptr || len == 0) - return true; - - ACE_Data_Block db(len, ACE_Message_Block::MB_DATA, (const char*)buf, nullptr, nullptr, ACE_Message_Block::DONT_DELETE, nullptr); - ACE_Message_Block message_block(&db, ACE_Message_Block::DONT_DELETE, nullptr); - - message_block.wr_ptr(len); - - if (msg_queue()->is_empty()) - { - // Try to send it directly. - ssize_t n = noblk_send(message_block); - - if (n < 0) - return false; - - size_t un = size_t(n); - if (un == len) - return true; - - // fall down - message_block.rd_ptr(un); - } - - ACE_Message_Block* mb = message_block.clone(); - - if (msg_queue()->enqueue_tail(mb, (ACE_Time_Value*)(&ACE_Time_Value::zero)) == -1) - { - mb->release(); - return false; - } - - if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - return false; - - return true; -} - -int RealmSocket::handle_output(ACE_HANDLE) -{ - if (closing_) - return -1; - - ACE_Message_Block* mb = nullptr; - - if (msg_queue()->is_empty()) - { - reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK); - return 0; - } - - if (msg_queue()->dequeue_head(mb, (ACE_Time_Value*)(&ACE_Time_Value::zero)) == -1) - return -1; - - ssize_t n = noblk_send(*mb); - - if (n < 0) - { - mb->release(); - return -1; - } - else if (size_t(n) == mb->length()) - { - mb->release(); - return 1; - } - else - { - mb->rd_ptr(n); - - if (msg_queue()->enqueue_head(mb, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1) - { - mb->release(); - return -1; - } - - return 0; - } - - ACE_NOTREACHED(return -1); -} - -int RealmSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask) -{ - // As opposed to WorldSocket::handle_close, we don't need locks here. - closing_ = true; - - if (h == ACE_INVALID_HANDLE) - peer().close_writer(); - - if (session_) - session_->OnClose(); - - reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK); - return 0; -} - -int RealmSocket::handle_input(ACE_HANDLE) -{ - if (closing_) - return -1; - - const ssize_t space = input_buffer_.space(); - - ssize_t n = peer().recv(input_buffer_.wr_ptr(), space); - - if (n < 0) - return errno == EWOULDBLOCK ? 0 : -1; - else if (n == 0) // EOF - return -1; - - input_buffer_.wr_ptr((size_t)n); - - if (session_ != nullptr) - { - session_->OnRead(); - input_buffer_.crunch(); - } - - // return 1 in case there is more data to read from OS - return n == space ? 1 : 0; -} - -void RealmSocket::set_session(Session* session) -{ - delete session_; - - session_ = session; -} diff --git a/src/server/shared/Network/RealmSocket.h b/src/server/shared/Network/RealmSocket.h deleted file mode 100644 index 53dc513ad5..0000000000 --- a/src/server/shared/Network/RealmSocket.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -#ifndef __REALMSOCKET_H__ -#define __REALMSOCKET_H__ - -#include "Common.h" -#include <ace/Message_Block.h> -#include <ace/SOCK_Stream.h> -#include <ace/Svc_Handler.h> -#include <ace/Synch_Traits.h> - -class RealmSocket : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> -{ -private: - typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> Base; - -public: - class Session - { - public: - Session(); - virtual ~Session(); - - virtual void OnRead() = 0; - virtual void OnAccept() = 0; - virtual void OnClose() = 0; - }; - - RealmSocket(); - ~RealmSocket() override; - - [[nodiscard]] size_t recv_len() const; - bool recv_soft(char* buf, size_t len); - bool recv(char* buf, size_t len); - void recv_skip(size_t len); - - bool send(const char* buf, size_t len); - - [[nodiscard]] const std::string& getRemoteAddress() const; - - [[nodiscard]] uint16 getRemotePort() const; - - int open(void*) override; - - int close(u_long) override; - - int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE) override; - int handle_output(ACE_HANDLE = ACE_INVALID_HANDLE) override; - - int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK) override; - - void set_session(Session* session); - -private: - ssize_t noblk_send(ACE_Message_Block& message_block); - - ACE_Message_Block input_buffer_; - Session* session_{nullptr}; - std::string _remoteAddress; - uint16 _remotePort{0}; -}; - -#endif /* __REALMSOCKET_H__ */ diff --git a/src/server/shared/Realms/Realm.cpp b/src/server/shared/Realms/Realm.cpp new file mode 100644 index 0000000000..4e114e0b6c --- /dev/null +++ b/src/server/shared/Realms/Realm.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + */ + +#include "Realm.h" +#include "IpAddress.h" +#include "IpNetwork.h" +#include <boost/asio/ip/tcp.hpp> + +boost::asio::ip::tcp_endpoint Realm::GetAddressForClient(boost::asio::ip::address const& clientAddr) const +{ + boost::asio::ip::address realmIp; + + // Attempt to send best address for client + if (clientAddr.is_loopback()) + { + // Try guessing if realm is also connected locally + if (LocalAddress->is_loopback() || ExternalAddress->is_loopback()) + { + realmIp = clientAddr; + } + else + { + // Assume that user connecting from the machine that bnetserver is located on + // has all realms available in his local network + realmIp = *LocalAddress; + } + } + else + { + if (clientAddr.is_v4() && Acore::Net::IsInNetwork(LocalAddress->to_v4(), LocalSubnetMask->to_v4(), clientAddr.to_v4())) + { + realmIp = *LocalAddress; + } + else + { + realmIp = *ExternalAddress; + } + } + + // Return external IP + return boost::asio::ip::tcp_endpoint(realmIp, Port); +} diff --git a/src/server/shared/Realms/Realm.h b/src/server/shared/Realms/Realm.h index e95e197c5a..00949c994b 100644 --- a/src/server/shared/Realms/Realm.h +++ b/src/server/shared/Realms/Realm.h @@ -6,9 +6,8 @@ #ifndef Realm_h__ #define Realm_h__ +#include "AsioHacksFwd.h" #include "Common.h" -// #include "AsioHacksFwd.h" -#include <ace/INET_Addr.h> enum RealmFlags { @@ -56,9 +55,9 @@ struct AC_SHARED_API Realm { RealmHandle Id; uint32 Build; - std::unique_ptr<ACE_INET_Addr> ExternalAddress; - std::unique_ptr<ACE_INET_Addr> LocalAddress; - std::unique_ptr<ACE_INET_Addr> LocalSubnetMask; + std::unique_ptr<boost::asio::ip::address> ExternalAddress; + std::unique_ptr<boost::asio::ip::address> LocalAddress; + std::unique_ptr<boost::asio::ip::address> LocalSubnetMask; uint16 Port; std::string Name; uint8 Type; @@ -67,7 +66,7 @@ struct AC_SHARED_API Realm AccountTypes AllowedSecurityLevel; float PopulationLevel; - // boost::asio::ip::tcp_endpoint GetAddressForClient(boost::asio::ip::address const& clientAddr) const; + boost::asio::ip::tcp_endpoint GetAddressForClient(boost::asio::ip::address const& clientAddr) const; }; #endif // Realm_h__ diff --git a/src/server/shared/Realms/RealmList.cpp b/src/server/shared/Realms/RealmList.cpp index 909c79e696..913dab1216 100644 --- a/src/server/shared/Realms/RealmList.cpp +++ b/src/server/shared/Realms/RealmList.cpp @@ -6,28 +6,38 @@ #include "RealmList.h" #include "DatabaseEnv.h" +#include "DeadlineTimer.h" +#include "IoContext.h" #include "Log.h" -#include "Optional.h" +#include "Resolver.h" #include "Util.h" +#include <boost/asio/ip/tcp.hpp> RealmList::RealmList() : _updateInterval(0) { } -RealmList* RealmList::instance() +RealmList* RealmList::Instance() { static RealmList instance; return &instance; } // Load the realm list from the database -void RealmList::Initialize(uint32 updateInterval) +void RealmList::Initialize(Acore::Asio::IoContext& ioContext, uint32 updateInterval) { _updateInterval = updateInterval; + _updateTimer = std::make_unique<Acore::Asio::DeadlineTimer>(ioContext); + _resolver = std::make_unique<Acore::Asio::Resolver>(ioContext); LoadBuildInfo(); // Get the content of the realmlist table in the database - UpdateRealms(); + UpdateRealms(boost::system::error_code()); +} + +void RealmList::Close() +{ + _updateTimer->cancel(); } void RealmList::LoadBuildInfo() @@ -72,7 +82,7 @@ void RealmList::LoadBuildInfo() } void RealmList::UpdateRealm(RealmHandle const& id, uint32 build, std::string const& name, - ACE_INET_Addr&& address, ACE_INET_Addr&& localAddr, ACE_INET_Addr&& localSubmask, + boost::asio::ip::address&& address, boost::asio::ip::address&& localAddr, boost::asio::ip::address&& localSubmask, uint16 port, uint8 icon, RealmFlags flag, uint8 timezone, AccountTypes allowedSecurityLevel, float population) { // Create new if not exist or update existed @@ -89,38 +99,30 @@ void RealmList::UpdateRealm(RealmHandle const& id, uint32 build, std::string con if (!realm.ExternalAddress || *realm.ExternalAddress != address) { - realm.ExternalAddress = std::make_unique<ACE_INET_Addr>(std::move(address)); + realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(std::move(address)); } if (!realm.LocalAddress || *realm.LocalAddress != localAddr) { - realm.LocalAddress = std::make_unique<ACE_INET_Addr>(std::move(localAddr)); + realm.LocalAddress = std::make_unique<boost::asio::ip::address>(std::move(localAddr)); } if (!realm.LocalSubnetMask || *realm.LocalSubnetMask != localSubmask) { - realm.LocalSubnetMask = std::make_unique<ACE_INET_Addr>(std::move(localSubmask)); + realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(std::move(localSubmask)); } realm.Port = port; } -void RealmList::UpdateIfNeed() +void RealmList::UpdateRealms(boost::system::error_code const& error) { - // maybe disabled or updated recently - if (!_updateInterval || _nextUpdateTime > time(nullptr)) + if (error) { + // Skip update if have errors return; } - _nextUpdateTime = time(nullptr) + _updateInterval; - - // Get the content of the realmlist table in the database - UpdateRealms(); -} - -void RealmList::UpdateRealms() -{ LOG_DEBUG("server.authserver", "Updating Realm List..."); LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALMLIST); @@ -139,69 +141,86 @@ void RealmList::UpdateRealms() { do { - Field* fields = result->Fetch(); - uint32 realmId = fields[0].GetUInt32(); - std::string name = fields[1].GetString(); - std::string externalAddressString = fields[2].GetString(); - std::string localAddressString = fields[3].GetString(); - std::string localSubmaskString = fields[4].GetString(); - uint16 port = fields[5].GetUInt16(); - - Optional<ACE_INET_Addr> externalAddress = ACE_INET_Addr(port, externalAddressString.c_str(), AF_INET); - if (!externalAddress) + try { - LOG_ERROR("server.authserver", "Could not resolve address %s for realm \"%s\" id %u", externalAddressString.c_str(), name.c_str(), realmId); - continue; + Field* fields = result->Fetch(); + uint32 realmId = fields[0].GetUInt32(); + std::string name = fields[1].GetString(); + std::string externalAddressString = fields[2].GetString(); + std::string localAddressString = fields[3].GetString(); + std::string localSubmaskString = fields[4].GetString(); + uint16 port = fields[5].GetUInt16(); + + Optional<boost::asio::ip::tcp::endpoint> externalAddress = _resolver->Resolve(boost::asio::ip::tcp::v4(), externalAddressString, ""); + if (!externalAddress) + { + LOG_ERROR("server.authserver", "Could not resolve address %s for realm \"%s\" id %u", externalAddressString.c_str(), name.c_str(), realmId); + continue; + } + + Optional<boost::asio::ip::tcp::endpoint> localAddress = _resolver->Resolve(boost::asio::ip::tcp::v4(), localAddressString, ""); + if (!localAddress) + { + LOG_ERROR("server.authserver", "Could not resolve localAddress %s for realm \"%s\" id %u", localAddressString.c_str(), name.c_str(), realmId); + continue; + } + + Optional<boost::asio::ip::tcp::endpoint> localSubmask = _resolver->Resolve(boost::asio::ip::tcp::v4(), localSubmaskString, ""); + if (!localSubmask) + { + LOG_ERROR("server.authserver", "Could not resolve localSubnetMask %s for realm \"%s\" id %u", localSubmaskString.c_str(), name.c_str(), realmId); + continue; + } + + uint8 icon = fields[6].GetUInt8(); + + if (icon == REALM_TYPE_FFA_PVP) + { + icon = REALM_TYPE_PVP; + } + + if (icon >= MAX_CLIENT_REALM_TYPE) + { + icon = REALM_TYPE_NORMAL; + } + + RealmFlags flag = RealmFlags(fields[7].GetUInt8()); + uint8 timezone = fields[8].GetUInt8(); + uint8 allowedSecurityLevel = fields[9].GetUInt8(); + float pop = fields[10].GetFloat(); + uint32 build = fields[11].GetUInt32(); + + RealmHandle id{ realmId }; + + UpdateRealm(id, build, name, externalAddress->address(), localAddress->address(), localSubmask->address(), port, icon, flag, + timezone, (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), pop); + + if (!existingRealms.count(id)) + { + LOG_INFO("server.authserver", "Added realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port); + } + else + { + LOG_DEBUG("server.authserver", "Updating realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port); + } + + existingRealms.erase(id); } - - Optional<ACE_INET_Addr> localAddress = ACE_INET_Addr(port, localAddressString.c_str(), AF_INET); - if (!localAddress) - { - LOG_ERROR("server.authserver", "Could not resolve localAddress %s for realm \"%s\" id %u", localAddressString.c_str(), name.c_str(), realmId); - continue; - } - - Optional<ACE_INET_Addr> localSubmask = ACE_INET_Addr(0, localSubmaskString.c_str(), AF_INET); - if (!localSubmask) - { - LOG_ERROR("server.authserver", "Could not resolve localSubnetMask %s for realm \"%s\" id %u", localSubmaskString.c_str(), name.c_str(), realmId); - continue; - } - - uint8 icon = fields[6].GetUInt8(); - - if (icon == REALM_TYPE_FFA_PVP) + catch (std::exception const& ex) { - icon = REALM_TYPE_PVP; + LOG_ERROR("server.authserver", "Realmlist::UpdateRealms has thrown an exception: %s", ex.what()); + ABORT(); } + } while (result->NextRow()); + } - if (icon >= MAX_CLIENT_REALM_TYPE) - { - icon = REALM_TYPE_NORMAL; - } - - RealmFlags flag = RealmFlags(fields[7].GetUInt8()); - uint8 timezone = fields[8].GetUInt8(); - uint8 allowedSecurityLevel = fields[9].GetUInt8(); - float pop = fields[10].GetFloat(); - uint32 build = fields[11].GetUInt32(); - - RealmHandle id{ realmId }; - - UpdateRealm(id, build, name, std::move(externalAddress.value()), std::move(localAddress.value()), std::move(localSubmask.value()), port, icon, flag, - timezone, (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), pop); + for (auto itr = existingRealms.begin(); itr != existingRealms.end(); ++itr) + LOG_INFO("server.authserver", "Removed realm \"%s\".", itr->second.c_str()); - if (!existingRealms.count(id)) - { - LOG_INFO("server.authserver", "Added realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port); - } - else - { - LOG_DEBUG("server.authserver", "Updating realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port); - } - - existingRealms.erase(id); - } while (result->NextRow()); + if (_updateInterval) + { + _updateTimer->expires_from_now(boost::posix_time::seconds(_updateInterval)); + _updateTimer->async_wait(std::bind(&RealmList::UpdateRealms, this, std::placeholders::_1)); } } diff --git a/src/server/shared/Realms/RealmList.h b/src/server/shared/Realms/RealmList.h index 938fb5aa36..a976cc3b51 100644 --- a/src/server/shared/Realms/RealmList.h +++ b/src/server/shared/Realms/RealmList.h @@ -11,8 +11,8 @@ #include "Realm.h" #include <array> #include <map> -#include <vector> #include <unordered_set> +#include <vector> struct RealmBuildInfo { @@ -25,19 +25,21 @@ struct RealmBuildInfo std::array<uint8, 20> MacHash; }; +namespace boost::system +{ + class error_code; +} + /// Storage object for the list of realms on the server class AC_SHARED_API RealmList { public: typedef std::map<RealmHandle, Realm> RealmMap; - RealmList(); - ~RealmList() = default; - - static RealmList* instance(); + static RealmList* Instance(); - void Initialize(uint32 updateInterval); - void UpdateIfNeed(); + void Initialize(Acore::Asio::IoContext& ioContext, uint32 updateInterval); + void Close(); RealmMap const& GetRealms() const { return _realms; } Realm const* GetRealm(RealmHandle const& id) const; @@ -45,18 +47,22 @@ public: RealmBuildInfo const* GetBuildInfo(uint32 build) const; private: + RealmList(); + ~RealmList() = default; + void LoadBuildInfo(); - void UpdateRealms(); + void UpdateRealms(boost::system::error_code const& error); void UpdateRealm(RealmHandle const& id, uint32 build, std::string const& name, - ACE_INET_Addr&& address, ACE_INET_Addr&& localAddr, ACE_INET_Addr&& localSubmask, + boost::asio::ip::address&& address, boost::asio::ip::address&& localAddr, boost::asio::ip::address&& localSubmask, uint16 port, uint8 icon, RealmFlags flag, uint8 timezone, AccountTypes allowedSecurityLevel, float population); std::vector<RealmBuildInfo> _builds; RealmMap _realms; uint32 _updateInterval; - time_t _nextUpdateTime; + std::unique_ptr<Acore::Asio::DeadlineTimer> _updateTimer; + std::unique_ptr<Acore::Asio::Resolver> _resolver; }; -#define sRealmList RealmList::instance() +#define sRealmList RealmList::Instance() #endif diff --git a/src/server/worldserver/CommandLine/CliRunnable.cpp b/src/server/worldserver/CommandLine/CliRunnable.cpp index bda81a18f5..9faf561c9e 100644 --- a/src/server/worldserver/CommandLine/CliRunnable.cpp +++ b/src/server/worldserver/CommandLine/CliRunnable.cpp @@ -12,7 +12,7 @@ #include "Chat.h" #include "CliRunnable.h" #include "Common.h" -#include "Configuration/Config.h" +#include "Config.h" #include "Language.h" #include "Log.h" #include "MapManager.h" @@ -124,7 +124,7 @@ int kb_hit_return() #endif /// %Thread start -void CliRunnable::run() +void CliThread() { ///- Display the list of available CLI functions then beep //TC_LOG_INFO("server.worldserver", ""); diff --git a/src/server/worldserver/CommandLine/CliRunnable.h b/src/server/worldserver/CommandLine/CliRunnable.h index 47b424b752..4730f513bd 100644 --- a/src/server/worldserver/CommandLine/CliRunnable.h +++ b/src/server/worldserver/CommandLine/CliRunnable.h @@ -11,14 +11,8 @@ #ifndef __CLIRUNNABLE_H #define __CLIRUNNABLE_H -#include "Threading.h" - /// Command Line Interface handling thread -class CliRunnable : public Acore::Runnable -{ -public: - void run() override; -}; +void CliThread(); #endif diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp index 2e458e8b44..5e6ad698d2 100644 --- a/src/server/worldserver/Main.cpp +++ b/src/server/worldserver/Main.cpp @@ -4,24 +4,49 @@ * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> */ -/// \addtogroup Trinityd Trinity Daemon -/// @{ -/// \file - +#include "ACSoap.h" #include "AppenderDB.h" +#include "AsyncAcceptor.h" +#include "AsyncAuctionListing.h" +#include "AvgDiffTracker.h" #include "Banner.h" +#include "BattlegroundMgr.h" +#include "BigNumber.h" +#include "CliRunnable.h" +#include "Common.h" +#include "Config.h" #include "Configuration/Config.h" -#include "Database/DatabaseEnv.h" -#include "Log.h" -#include "Master.h" +#include "DatabaseEnv.h" +#include "DatabaseLoader.h" +#include "DeadlineTimer.h" +#include "GitRevision.h" +#include "IoContext.h" +#include "MapManager.h" +#include "MySQLThreading.h" +#include "ObjectAccessor.h" +#include "OpenSSLCrypto.h" +#include "OutdoorPvPMgr.h" +#include "ProcessPriority.h" +#include "RASession.h" +#include "RealmList.h" +#include "Resolver.h" +#include "ScriptLoader.h" +#include "ScriptMgr.h" +#include "SecretMgr.h" #include "SharedDefines.h" -#include <ace/Version.h> -#include <boost/version.hpp> +#include "World.h" +#include "WorldSocket.h" +#include "WorldSocketMgr.h" +#include <boost/asio/signal_set.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/program_options.hpp> +#include <csignal> +#include <iostream> #include <openssl/crypto.h> #include <openssl/opensslv.h> -#ifndef _ACORE_CORE_CONFIG -#define _ACORE_CORE_CONFIG "worldserver.conf" +#ifdef ELUNA +#include "LuaEngine.h" #endif #ifdef _WIN32 @@ -38,6 +63,44 @@ char serviceDescription[] = "AzerothCore World of Warcraft emulator world servic int m_ServiceStatus = -1; #endif +#ifndef _ACORE_CORE_CONFIG +#define _ACORE_CORE_CONFIG "worldserver.conf" +#endif + +#define WORLD_SLEEP_CONST 10 + +class FreezeDetector +{ +public: + FreezeDetector(Acore::Asio::IoContext& ioContext, uint32 maxCoreStuckTime) + : _timer(ioContext), _worldLoopCounter(0), _lastChangeMsTime(getMSTime()), _maxCoreStuckTimeInMs(maxCoreStuckTime) { } + + static void Start(std::shared_ptr<FreezeDetector> const& freezeDetector) + { + freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(5)); + freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, std::weak_ptr<FreezeDetector>(freezeDetector), std::placeholders::_1)); + } + + static void Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error); + +private: + Acore::Asio::DeadlineTimer _timer; + uint32 _worldLoopCounter; + uint32 _lastChangeMsTime; + uint32 _maxCoreStuckTimeInMs; +}; + +void SignalHandler(boost::system::error_code const& error, int signalNumber); +void ClearOnlineAccounts(); +bool StartDB(); +void StopDB(); +bool LoadRealmInfo(Acore::Asio::IoContext& ioContext); +AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext); +void ShutdownCLIThread(std::thread* cliThread); +void AuctionListingRunnable(); +void ShutdownAuctionListingThread(std::thread* thread); +void WorldUpdateLoop(); + /// Print out the usage string for this program on the console. void usage(const char* prog) { @@ -52,10 +115,11 @@ void usage(const char* prog) #endif } -/// Launch the Trinity server -extern int main(int argc, char** argv) +/// Launch the Azeroth server +int main(int argc, char** argv) { Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_WORLDSERVER; + signal(SIGABRT, &Acore::AbortHandler); ///- Command line parsing to get the configuration file name std::string configFile = sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG); @@ -124,6 +188,9 @@ extern int main(int argc, char** argv) // Loading modules configs sConfigMgr->LoadModulesConfigs(); + std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>(); + + // Init all logs sLog->RegisterAppender<AppenderDB>(); sLog->Initialize(); @@ -134,23 +201,541 @@ extern int main(int argc, char** argv) }, []() { - LOG_INFO("server.worldserver", "> Using configuration file %s.", sConfigMgr->GetFilename().c_str()); - LOG_INFO("server.worldserver", "> Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); - LOG_INFO("server.worldserver", "> Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); - LOG_INFO("server.worldserver", "> Using ACE version: %s\n", ACE_VERSION); + LOG_INFO("server.worldserver", "> Using configuration file: %s", sConfigMgr->GetFilename().c_str()); + LOG_INFO("server.worldserver", "> Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); + LOG_INFO("server.worldserver", "> Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); } ); - ///- and run the 'Master' - /// @todo Why do we need this 'Master'? Can't all of this be in the Main as for Realmd? - int ret = sMaster->Run(); + OpenSSLCrypto::threadsSetup(); + + std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); }); + + // Seed the OpenSSL's PRNG here. + // That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login + BigNumber seed; + seed.SetRand(16 * 8); + + /// worldserver PID file creation + std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", ""); + if (!pidFile.empty()) + { + if (uint32 pid = CreatePIDFile(pidFile)) + LOG_ERROR("server", "Daemon PID: %u\n", pid); // outError for red color in console + else + { + LOG_ERROR("server", "Cannot create PID file %s (possible error: permission)\n", pidFile.c_str()); + return 1; + } + } + + // Set signal handlers (this must be done before starting IoContext threads, because otherwise they would unblock and exit) + boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM); +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + signals.add(SIGBREAK); +#endif + signals.async_wait(SignalHandler); + + // Start the Boost based thread pool + int numThreads = sConfigMgr->GetOption<int32>("ThreadPool", 1); + std::shared_ptr<std::vector<std::thread>> threadPool(new std::vector<std::thread>(), [ioContext](std::vector<std::thread>* del) + { + ioContext->stop(); + for (std::thread& thr : *del) + thr.join(); + + delete del; + }); + + if (numThreads < 1) + { + numThreads = 1; + } + + for (int i = 0; i < numThreads; ++i) + { + threadPool->push_back(std::thread([ioContext]() + { + ioContext->run(); + })); + } + + // Set process priority according to configuration settings + SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false)); + + // Start the databases + if (!StartDB()) + return 1; + + std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); }); + + // set server offline (not connectable) + LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm); + + LoadRealmInfo(*ioContext); + + // Loading modules configs + sConfigMgr->PrintLoadedModulesConfigs(); + + sScriptMgr->SetScriptLoader(AddScripts); + std::shared_ptr<void> sScriptMgrHandle(nullptr, [](void*) + { + sScriptMgr->Unload(); + //sScriptReloadMgr->Unload(); + }); + + ///- Initialize the World + sSecretMgr->Initialize(); + sWorld->SetInitialWorldSettings(); + + std::shared_ptr<void> mapManagementHandle(nullptr, [](void*) + { + // unload battleground templates before different singletons destroyed + sBattlegroundMgr->DeleteAllBattlegrounds(); + + sOutdoorPvPMgr->Die(); // unload it before MapManager + sMapMgr->UnloadAll(); // unload all grids (including locked in memory) + +#ifdef ELUNA + Eluna::Uninitialize(); +#endif + }); + + // Start the Remote Access port (acceptor) if enabled + std::unique_ptr<AsyncAcceptor> raAcceptor; + if (sConfigMgr->GetOption<bool>("Ra.Enable", false)) + { + raAcceptor.reset(StartRaSocketAcceptor(*ioContext)); + } + + // Start soap serving thread if enabled + std::shared_ptr<std::thread> soapThread; + if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false)) + { + soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetOption<int32>("SOAP.Port", 7878))), + [](std::thread* thr) + { + thr->join(); + delete thr; + }); + } + + // Launch the worldserver listener socket + uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD)); + std::string worldListener = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0"); + + int networkThreads = sConfigMgr->GetOption<int32>("Network.Threads", 1); + + if (networkThreads <= 0) + { + LOG_ERROR("server.worldserver", "Network.Threads must be greater than 0"); + World::StopNow(ERROR_EXIT_CODE); + return 1; + } + + if (!sWorldSocketMgr.StartWorldNetwork(*ioContext, worldListener, worldPort, networkThreads)) + { + LOG_ERROR("server.worldserver", "Failed to initialize network"); + World::StopNow(ERROR_EXIT_CODE); + return 1; + } + + std::shared_ptr<void> sWorldSocketMgrHandle(nullptr, [](void*) + { + sWorld->KickAll(); // save and kick all players + sWorld->UpdateSessions(1); // real players unload required UpdateSessions call + + sWorldSocketMgr.StopNetwork(); + + ///- Clean database before leaving + ClearOnlineAccounts(); + }); + + // Set server online (allow connecting now) + LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm); + realm.PopulationLevel = 0.0f; + realm.Flags = RealmFlags(realm.Flags & ~uint32(REALM_FLAG_VERSION_MISMATCH)); + + // Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec) + std::shared_ptr<FreezeDetector> freezeDetector; + if (int32 coreStuckTime = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 60)) + { + freezeDetector = std::make_shared<FreezeDetector>(*ioContext, coreStuckTime * 1000); + FreezeDetector::Start(freezeDetector); + LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", coreStuckTime); + } + + LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", GitRevision::GetFullVersion()); + + sScriptMgr->OnStartup(); + + // Launch CliRunnable thread + std::shared_ptr<std::thread> cliThread; +#ifdef _WIN32 + if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/) +#else + if (sConfigMgr->GetOption<bool>("Console.Enable", true)) +#endif + { + cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread); + } + + // Launch CliRunnable thread + std::shared_ptr<std::thread> auctionLisingThread; + auctionLisingThread.reset(new std::thread(AuctionListingRunnable), + [](std::thread* thr) + { + thr->join(); + delete thr; + }); + + WorldUpdateLoop(); + + // Shutdown starts here + threadPool.reset(); + + sScriptMgr->OnShutdown(); + + // set server offline + LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm); + + LOG_INFO("server.worldserver", "Halting process..."); - // at sMaster return function exist with codes // 0 - normal shutdown // 1 - shutdown at error - // 2 - restart command used, this code can be used by restarter for restart Trinityd + // 2 - restart command used, this code can be used by restarter for restart Warheadd + + return World::GetExitCode(); +} + +/// Initialize connection to the databases +bool StartDB() +{ + MySQL::Library_Init(); + + // Load databases + DatabaseLoader loader("server.worldserver"); + loader + .AddDatabase(LoginDatabase, "Login") + .AddDatabase(CharacterDatabase, "Character") + .AddDatabase(WorldDatabase, "World"); + + if (!loader.Load()) + return false; + + ///- Get the realm Id from the configuration file + realm.Id.Realm = sConfigMgr->GetIntDefault("RealmID", 0); + if (!realm.Id.Realm) + { + LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file"); + return false; + } + else if (realm.Id.Realm > 255) + { + /* + * Due to the client only being able to read a realm.Id.Realm + * with a size of uint8 we can "only" store up to 255 realms + * anything further the client will behave anormaly + */ + LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255"); + return false; + } + + LOG_INFO("server.loading", "Loading world information..."); + LOG_INFO("server.loading", "> RealmID: %u", realm.Id.Realm); + + ///- Clean the database before starting + ClearOnlineAccounts(); + + ///- Insert version info into DB + WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // One-time query + + sWorld->LoadDBVersion(); + sWorld->LoadDBRevision(); + + LOG_INFO("server.loading", "> Version DB world: %s", sWorld->GetDBVersion()); - return ret; + return true; } -/// @} +void StopDB() +{ + CharacterDatabase.Close(); + WorldDatabase.Close(); + LoginDatabase.Close(); + + MySQL::Library_End(); +} + +/// Clear 'online' status for all accounts with characters in this realm +void ClearOnlineAccounts() +{ + // Reset online status for all accounts with characters on the current realm + // pussywizard: tc query would set online=0 even if logged in on another realm >_> + LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online = %u", realm.Id.Realm); + + // Reset online status for all characters + CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0"); +} + +void ShutdownCLIThread(std::thread* cliThread) +{ + if (cliThread != nullptr) + { +#ifdef _WIN32 + // First try to cancel any I/O in the CLI thread + if (!CancelSynchronousIo(cliThread->native_handle())) + { + // if CancelSynchronousIo() fails, print the error and try with old way + DWORD errorCode = GetLastError(); + LPCSTR errorBuffer; + + DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr); + if (!formatReturnCode) + errorBuffer = "Unknown error"; + + LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code %u, detail: %s", uint32(errorCode), errorBuffer); + + if (!formatReturnCode) + LocalFree((LPSTR)errorBuffer); + + // send keyboard input to safely unblock the CLI thread + INPUT_RECORD b[4]; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + b[0].EventType = KEY_EVENT; + b[0].Event.KeyEvent.bKeyDown = TRUE; + b[0].Event.KeyEvent.uChar.AsciiChar = 'X'; + b[0].Event.KeyEvent.wVirtualKeyCode = 'X'; + b[0].Event.KeyEvent.wRepeatCount = 1; + + b[1].EventType = KEY_EVENT; + b[1].Event.KeyEvent.bKeyDown = FALSE; + b[1].Event.KeyEvent.uChar.AsciiChar = 'X'; + b[1].Event.KeyEvent.wVirtualKeyCode = 'X'; + b[1].Event.KeyEvent.wRepeatCount = 1; + + b[2].EventType = KEY_EVENT; + b[2].Event.KeyEvent.bKeyDown = TRUE; + b[2].Event.KeyEvent.dwControlKeyState = 0; + b[2].Event.KeyEvent.uChar.AsciiChar = '\r'; + b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; + b[2].Event.KeyEvent.wRepeatCount = 1; + b[2].Event.KeyEvent.wVirtualScanCode = 0x1c; + + b[3].EventType = KEY_EVENT; + b[3].Event.KeyEvent.bKeyDown = FALSE; + b[3].Event.KeyEvent.dwControlKeyState = 0; + b[3].Event.KeyEvent.uChar.AsciiChar = '\r'; + b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; + b[3].Event.KeyEvent.wVirtualScanCode = 0x1c; + b[3].Event.KeyEvent.wRepeatCount = 1; + DWORD numb; + WriteConsoleInput(hStdIn, b, 4, &numb); + } +#endif + cliThread->join(); + delete cliThread; + } +} + +void WorldUpdateLoop() +{ + uint32 realCurrTime = 0; + uint32 realPrevTime = getMSTime(); + + LoginDatabase.WarnAboutSyncQueries(true); + CharacterDatabase.WarnAboutSyncQueries(true); + WorldDatabase.WarnAboutSyncQueries(true); + + ///- While we have not World::m_stopEvent, update the world + while (!World::IsStopped()) + { + ++World::m_worldLoopCounter; + realCurrTime = getMSTime(); + + uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime); + + sWorld->Update(diff); + realPrevTime = realCurrTime; + + uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime()); + devDiffTracker.Update(executionTimeDiff); + avgDiffTracker.Update(executionTimeDiff > WORLD_SLEEP_CONST ? executionTimeDiff : WORLD_SLEEP_CONST); + + // we know exactly how long it took to update the world, if the update took less than WORLD_SLEEP_CONST, sleep for WORLD_SLEEP_CONST - world update time + if (executionTimeDiff < WORLD_SLEEP_CONST) + { + std::this_thread::sleep_for(Milliseconds(WORLD_SLEEP_CONST - executionTimeDiff)); + } + +#ifdef _WIN32 + if (m_ServiceStatus == 0) + World::StopNow(SHUTDOWN_EXIT_CODE); + + while (m_ServiceStatus == 2) + Sleep(1000); +#endif + } + + LoginDatabase.WarnAboutSyncQueries(false); + CharacterDatabase.WarnAboutSyncQueries(false); + WorldDatabase.WarnAboutSyncQueries(false); +} + +void SignalHandler(boost::system::error_code const& error, int /*signalNumber*/) +{ + if (!error) + World::StopNow(SHUTDOWN_EXIT_CODE); +} + +void FreezeDetector::Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error) +{ + if (!error) + { + if (std::shared_ptr<FreezeDetector> freezeDetector = freezeDetectorRef.lock()) + { + uint32 curtime = getMSTime(); + + uint32 worldLoopCounter = World::m_worldLoopCounter; + if (freezeDetector->_worldLoopCounter != worldLoopCounter) + { + freezeDetector->_lastChangeMsTime = curtime; + freezeDetector->_worldLoopCounter = worldLoopCounter; + } + // possible freeze + else if (getMSTimeDiff(freezeDetector->_lastChangeMsTime, curtime) > freezeDetector->_maxCoreStuckTimeInMs) + { + LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!"); + ABORT(); + } + + freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(1)); + freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, freezeDetectorRef, std::placeholders::_1)); + } + } +} + +AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext) +{ + uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443)); + std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0"); + + AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort); + if (!acceptor->Bind()) + { + LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor"); + delete acceptor; + return nullptr; + } + + acceptor->AsyncAccept<RASession>(); + return acceptor; +} + +bool LoadRealmInfo(Acore::Asio::IoContext& ioContext) +{ + QueryResult result = LoginDatabase.PQuery("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = %u", realm.Id.Realm); + if (!result) + return false; + + Acore::Asio::Resolver resolver(ioContext); + + Field* fields = result->Fetch(); + realm.Name = fields[1].GetString(); + + Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[2].GetString(), ""); + if (!externalAddress) + { + LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[2].GetString().c_str()); + return false; + } + + realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(externalAddress->address()); + + Optional<boost::asio::ip::tcp::endpoint> localAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[3].GetString(), ""); + if (!localAddress) + { + LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[3].GetString().c_str()); + return false; + } + + realm.LocalAddress = std::make_unique<boost::asio::ip::address>(localAddress->address()); + + Optional<boost::asio::ip::tcp::endpoint> localSubmask = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[4].GetString(), ""); + if (!localSubmask) + { + LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[4].GetString().c_str()); + return false; + } + + realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(localSubmask->address()); + + realm.Port = fields[5].GetUInt16(); + realm.Type = fields[6].GetUInt8(); + realm.Flags = RealmFlags(fields[7].GetUInt8()); + realm.Timezone = fields[8].GetUInt8(); + realm.AllowedSecurityLevel = AccountTypes(fields[9].GetUInt8()); + realm.PopulationLevel = fields[10].GetFloat(); + realm.Build = fields[11].GetUInt32(); + return true; +} + +void AuctionListingRunnable() +{ + LOG_INFO("server", "Starting up Auction House Listing thread..."); + + while (!World::IsStopped()) + { + if (AsyncAuctionListingMgr::IsAuctionListingAllowed()) + { + uint32 diff = AsyncAuctionListingMgr::GetDiff(); + AsyncAuctionListingMgr::ResetDiff(); + + if (AsyncAuctionListingMgr::GetTempList().size() || AsyncAuctionListingMgr::GetList().size()) + { + std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetLock()); + + { + std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetTempLock()); + + for (auto const& delayEvent : AsyncAuctionListingMgr::GetTempList()) + AsyncAuctionListingMgr::GetList().emplace_back(delayEvent); + + AsyncAuctionListingMgr::GetTempList().clear(); + } + + for (auto& itr : AsyncAuctionListingMgr::GetList()) + { + if (itr._msTimer <= diff) + itr._msTimer = 0; + else + itr._msTimer -= diff; + } + + for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr) + { + if ((*itr)._msTimer != 0) + continue; + + if ((*itr).Execute()) + AsyncAuctionListingMgr::GetList().erase(itr); + + break; + } + } + } + std::this_thread::sleep_for(1ms); + } + + LOG_INFO("server", "Auction House Listing thread exiting without problems."); +} + +void ShutdownAuctionListingThread(std::thread* thread) +{ + if (thread != nullptr) + { + thread->join(); + delete thread; + } +} diff --git a/src/server/worldserver/Master.cpp b/src/server/worldserver/Master.cpp deleted file mode 100644 index 81fc7cddd4..0000000000 --- a/src/server/worldserver/Master.cpp +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -/** \file - \ingroup Trinityd -*/ - -#include "ACSoap.h" -#include "AsyncAcceptor.h" -#include "BigNumber.h" -#include "CliRunnable.h" -#include "Common.h" -#include "Config.h" -#include "DatabaseEnv.h" -#include "GitRevision.h" -#include "IoContext.h" -#include "Log.h" -#include "Master.h" -#include "OpenSSLCrypto.h" -#include "RASession.h" -#include "Realm.h" -#include "ScriptMgr.h" -#include "SignalHandler.h" -#include "ScriptLoader.h" -#include "Timer.h" -#include "Util.h" -#include "World.h" -#include "WorldRunnable.h" -#include "WorldSocket.h" -#include "WorldSocketMgr.h" -#include "DatabaseLoader.h" -#include "Optional.h" -#include "MySQLThreading.h" -#include "SecretMgr.h" -#include "ProcessPriority.h" -#include <ace/Sig_Handler.h> - -#ifdef _WIN32 -#include "ServiceWin32.h" -extern int m_ServiceStatus; -#endif - -/// Handle worldservers's termination signals -void HandleSignal(int sigNum) -{ - switch (sigNum) - { - case SIGINT: - World::StopNow(RESTART_EXIT_CODE); - break; - case SIGTERM: -#if AC_PLATFORM == AC_PLATFORM_WINDOWS - case SIGBREAK: - if (m_ServiceStatus != 1) -#endif - World::StopNow(SHUTDOWN_EXIT_CODE); - break; - default: - break; - } -} - -class FreezeDetectorRunnable : public Acore::Runnable -{ -private: - uint32 _loops; - uint32 _lastChange; - uint32 _delayTime; - -public: - FreezeDetectorRunnable(uint32 freezeDelay) : _loops(0), _lastChange(0), _delayTime(freezeDelay) {} - - void run() override - { - if (!_delayTime) - return; - - LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", _delayTime / 1000); - while (!World::IsStopped()) - { - uint32 curtime = getMSTime(); - if (_loops != World::m_worldLoopCounter) - { - _lastChange = curtime; - _loops = World::m_worldLoopCounter; - } - else if (getMSTimeDiff(_lastChange, curtime) > _delayTime) - { - LOG_INFO("server.worldserver", "World Thread hangs, kicking out server!"); - ABORT(); - } - - Acore::Thread::Sleep(1000); - } - LOG_INFO("server.worldserver", "Anti-freeze thread exiting without problems."); - } -}; - -bool LoadRealmInfo(); -AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext); - -Master* Master::instance() -{ - static Master instance; - return &instance; -} - -/// Main function -int Master::Run() -{ - std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>(); - - OpenSSLCrypto::threadsSetup(); - BigNumber seed1; - seed1.SetRand(16 * 8); - - /// worldserver PID file creation - std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", ""); - if (!pidFile.empty()) - { - if (uint32 pid = CreatePIDFile(pidFile)) - LOG_ERROR("server.worldserver", "Daemon PID: %u\n", pid); // outError for red color in console - else - { - LOG_ERROR("server.worldserver", "Cannot create PID file %s (possible error: permission)\n", pidFile.c_str()); - return 1; - } - } - - // Set process priority according to configuration settings - SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false)); - - ///- Start the databases - if (!_StartDB()) - return 1; - - // set server offline (not connectable) - LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm); - - LoadRealmInfo(); - - // Loading modules configs - sConfigMgr->PrintLoadedModulesConfigs(); - - ///- Initialize the World - sSecretMgr->Initialize(); - sScriptMgr->SetScriptLoader(AddScripts); - sWorld->SetInitialWorldSettings(); - - sScriptMgr->OnStartup(); - - ///- Initialize the signal handlers - Acore::SignalHandler signalHandler; - - signalHandler.handle_signal(SIGINT, &HandleSignal); - signalHandler.handle_signal(SIGTERM, &HandleSignal); - -#if AC_PLATFORM == AC_PLATFORM_WINDOWS - signalHandler.handle_signal(SIGBREAK, &HandleSignal); -#endif - - ///- Launch WorldRunnable thread - Acore::Thread worldThread(new WorldRunnable); - worldThread.setPriority(Acore::Priority_Highest); - - Acore::Thread* cliThread = nullptr; - -#ifdef _WIN32 - if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/) -#else - if (sConfigMgr->GetOption<bool>("Console.Enable", true)) -#endif - { - ///- Launch CliRunnable thread - cliThread = new Acore::Thread(new CliRunnable); - } - - // pussywizard: - Acore::Thread auctionLising_thread(new AuctionListingRunnable); - auctionLising_thread.setPriority(Acore::Priority_High); - - // Start the Remote Access port (acceptor) if enabled - std::unique_ptr<AsyncAcceptor> raAcceptor; - if (sConfigMgr->GetOption<bool>("Ra.Enable", false)) - raAcceptor.reset(StartRaSocketAcceptor(*ioContext)); - - // Start soap serving thread if enabled - std::shared_ptr<std::thread> soapThread; - if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false)) - { - soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), sConfigMgr->GetOption<uint16>("SOAP.Port", 7878)), - [](std::thread* thr) - { - thr->join(); - delete thr; - }); - } - - // Start up freeze catcher thread - Acore::Thread* freezeThread = nullptr; - if (uint32 freezeDelay = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 0)) - { - FreezeDetectorRunnable* runnable = new FreezeDetectorRunnable(freezeDelay * 1000); - freezeThread = new Acore::Thread(runnable); - freezeThread->setPriority(Acore::Priority_Highest); - } - - ///- Launch the world listener socket - uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD)); - std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0"); - if (sWorldSocketMgr->StartNetwork(worldPort, bindIp.c_str()) == -1) - { - LOG_ERROR("server.worldserver", "Failed to start network"); - World::StopNow(ERROR_EXIT_CODE); - // go down and shutdown the server - } - - // set server online (allow connecting now) - LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm); - - LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", GitRevision::GetFullVersion()); - - // when the main thread closes the singletons get unloaded - // since worldrunnable uses them, it will crash if unloaded after master - worldThread.wait(); - auctionLising_thread.wait(); - - if (freezeThread) - { - freezeThread->wait(); - delete freezeThread; - } - - // set server offline - LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm); - - ///- Clean database before leaving - ClearOnlineAccounts(); - - _StopDB(); - - LOG_INFO("server.worldserver", "Halting process..."); - - if (cliThread) - { -#ifdef _WIN32 - - // this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API) - //_exit(1); - // send keyboard input to safely unblock the CLI thread - INPUT_RECORD b[4]; - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - b[0].EventType = KEY_EVENT; - b[0].Event.KeyEvent.bKeyDown = TRUE; - b[0].Event.KeyEvent.uChar.AsciiChar = 'X'; - b[0].Event.KeyEvent.wVirtualKeyCode = 'X'; - b[0].Event.KeyEvent.wRepeatCount = 1; - - b[1].EventType = KEY_EVENT; - b[1].Event.KeyEvent.bKeyDown = FALSE; - b[1].Event.KeyEvent.uChar.AsciiChar = 'X'; - b[1].Event.KeyEvent.wVirtualKeyCode = 'X'; - b[1].Event.KeyEvent.wRepeatCount = 1; - - b[2].EventType = KEY_EVENT; - b[2].Event.KeyEvent.bKeyDown = TRUE; - b[2].Event.KeyEvent.dwControlKeyState = 0; - b[2].Event.KeyEvent.uChar.AsciiChar = '\r'; - b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; - b[2].Event.KeyEvent.wRepeatCount = 1; - b[2].Event.KeyEvent.wVirtualScanCode = 0x1c; - - b[3].EventType = KEY_EVENT; - b[3].Event.KeyEvent.bKeyDown = FALSE; - b[3].Event.KeyEvent.dwControlKeyState = 0; - b[3].Event.KeyEvent.uChar.AsciiChar = '\r'; - b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; - b[3].Event.KeyEvent.wVirtualScanCode = 0x1c; - b[3].Event.KeyEvent.wRepeatCount = 1; - DWORD numb; - WriteConsoleInput(hStdIn, b, 4, &numb); - - cliThread->wait(); - -#else - - cliThread->destroy(); - -#endif - - delete cliThread; - } - - // for some unknown reason, unloading scripts here and not in worldrunnable - // fixes a memory leak related to detaching threads from the module - //UnloadScriptingModule(); - - OpenSSLCrypto::threadsCleanup(); - // Exit the process with specified return value - return World::GetExitCode(); -} - -/// Initialize connection to the databases -bool Master::_StartDB() -{ - MySQL::Library_Init(); - - // Load databases - DatabaseLoader loader("server.worldserver"); - loader - .AddDatabase(LoginDatabase, "Login") - .AddDatabase(CharacterDatabase, "Character") - .AddDatabase(WorldDatabase, "World"); - - if (!loader.Load()) - return false; - - ///- Get the realm Id from the configuration file - realm.Id.Realm = sConfigMgr->GetOption<int32>("RealmID", 0); - if (!realm.Id.Realm) - { - LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file"); - return false; - } - else if (realm.Id.Realm > 255) - { - /* - * Due to the client only being able to read a realm.Id.Realm - * with a size of uint8 we can "only" store up to 255 realms - * anything further the client will behave anormaly - */ - LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255"); - return false; - } - - LOG_INFO("server.worldserver", "Realm running as realm ID %d", realm.Id.Realm); - - ///- Clean the database before starting - ClearOnlineAccounts(); - - ///- Insert version info into DB - WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // One-time query - - sWorld->LoadDBVersion(); - sWorld->LoadDBRevision(); - - LOG_INFO("server.worldserver", "Using World DB: %s", sWorld->GetDBVersion()); - return true; -} - -void Master::_StopDB() -{ - CharacterDatabase.Close(); - WorldDatabase.Close(); - LoginDatabase.Close(); - - MySQL::Library_End(); -} - -/// Clear 'online' status for all accounts with characters in this realm -void Master::ClearOnlineAccounts() -{ - // Reset online status for all accounts with characters on the current realm - // pussywizard: tc query would set online=0 even if logged in on another realm >_> - LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online = %u", realm.Id.Realm); - - // Reset online status for all characters - CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0"); -} - -bool LoadRealmInfo() -{ - QueryResult result = LoginDatabase.PQuery("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = %u", realm.Id.Realm); - if (!result) - { - LOG_ERROR("server.worldserver", "> Not found realm with ID %u", realm.Id.Realm); - return false; - } - - Field* fields = result->Fetch(); - realm.Name = fields[1].GetString(); - realm.Port = fields[5].GetUInt16(); - - Optional<ACE_INET_Addr> externalAddress = ACE_INET_Addr(realm.Port, fields[2].GetCString(), AF_INET); - if (!externalAddress) - { - LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[2].GetString().c_str()); - return false; - } - - Optional<ACE_INET_Addr> localAddress = ACE_INET_Addr(realm.Port, fields[3].GetCString(), AF_INET); - if (!localAddress) - { - LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[3].GetString().c_str()); - return false; - } - - Optional<ACE_INET_Addr> localSubmask = ACE_INET_Addr(0, fields[4].GetCString(), AF_INET); - if (!localSubmask) - { - LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[4].GetString().c_str()); - return false; - } - - realm.ExternalAddress = std::make_unique<ACE_INET_Addr>(*externalAddress); - realm.LocalAddress = std::make_unique<ACE_INET_Addr>(*localAddress); - realm.LocalSubnetMask = std::make_unique<ACE_INET_Addr>(*localSubmask); - - realm.Type = fields[6].GetUInt8(); - realm.Flags = RealmFlags(fields[7].GetUInt8()); - realm.Timezone = fields[8].GetUInt8(); - realm.AllowedSecurityLevel = AccountTypes(fields[9].GetUInt8()); - realm.PopulationLevel = fields[10].GetFloat(); - realm.Build = fields[11].GetUInt32(); - return true; -} - -AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext) -{ - uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443)); - std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0"); - - AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort); - if (!acceptor->Bind()) - { - LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor"); - delete acceptor; - return nullptr; - } - - acceptor->AsyncAccept<RASession>(); - return acceptor; -} diff --git a/src/server/worldserver/Master.h b/src/server/worldserver/Master.h deleted file mode 100644 index fff192516b..0000000000 --- a/src/server/worldserver/Master.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -/// \addtogroup Trinityd -/// @{ -/// \file - -#ifndef _MASTER_H -#define _MASTER_H - -#include "Common.h" - -/// Start the server -class Master -{ -public: - static Master* instance(); - int Run(); - -private: - bool _StartDB(); - void _StopDB(); - - void ClearOnlineAccounts(); -}; - -#define sMaster Master::instance() - -#endif - -/// @} diff --git a/src/server/worldserver/WorldThread/WorldRunnable.cpp b/src/server/worldserver/WorldThread/WorldRunnable.cpp deleted file mode 100644 index 9c99a7a7fa..0000000000 --- a/src/server/worldserver/WorldThread/WorldRunnable.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -/** \file - \ingroup Trinityd -*/ - -#include "AsyncAuctionListing.h" -#include "AvgDiffTracker.h" -#include "BattlegroundMgr.h" -#include "Common.h" -#include "Database/DatabaseEnv.h" -#include "MapManager.h" -#include "ObjectAccessor.h" -#include "OutdoorPvPMgr.h" -#include "ScriptMgr.h" -#include "Timer.h" -#include "World.h" -#include "WorldRunnable.h" -#include "WorldSocketMgr.h" - -#ifdef ELUNA -#include "LuaEngine.h" -#endif - -#ifdef _WIN32 -#include "ServiceWin32.h" -extern int m_ServiceStatus; -#endif - -/// Heartbeat for the World -void WorldRunnable::run() -{ - uint32 realCurrTime = 0; - uint32 realPrevTime = getMSTime(); - - ///- While we have not World::m_stopEvent, update the world - while (!World::IsStopped()) - { - ++World::m_worldLoopCounter; - realCurrTime = getMSTime(); - - uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime); - - sWorld->Update( diff ); - realPrevTime = realCurrTime; - - uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime()); - devDiffTracker.Update(executionTimeDiff); - avgDiffTracker.Update(executionTimeDiff > WORLD_SLEEP_CONST ? executionTimeDiff : WORLD_SLEEP_CONST); - - if (executionTimeDiff < WORLD_SLEEP_CONST) - Acore::Thread::Sleep(WORLD_SLEEP_CONST - executionTimeDiff); - -#ifdef _WIN32 - if (m_ServiceStatus == 0) - World::StopNow(SHUTDOWN_EXIT_CODE); - - while (m_ServiceStatus == 2) - Sleep(1000); -#endif - } - - sScriptMgr->OnShutdown(); - - sWorld->KickAll(); // save and kick all players - sWorld->UpdateSessions( 1 ); // real players unload required UpdateSessions call - - // unload battleground templates before different singletons destroyed - sBattlegroundMgr->DeleteAllBattlegrounds(); - - sWorldSocketMgr->StopNetwork(); - - sMapMgr->UnloadAll(); // unload all grids (including locked in memory) - sOutdoorPvPMgr->Die(); - sScriptMgr->Unload(); -#ifdef ELUNA - Eluna::Uninitialize(); -#endif -} - -void AuctionListingRunnable::run() -{ - LOG_INFO("auctionHouse", "Starting up Auction House Listing thread..."); - while (!World::IsStopped()) - { - if (AsyncAuctionListingMgr::IsAuctionListingAllowed()) - { - uint32 diff = AsyncAuctionListingMgr::GetDiff(); - AsyncAuctionListingMgr::ResetDiff(); - - if (AsyncAuctionListingMgr::GetTempList().size() || AsyncAuctionListingMgr::GetList().size()) - { - std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetLock()); - - { - std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetTempLock()); - for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetTempList().begin(); itr != AsyncAuctionListingMgr::GetTempList().end(); ++itr) - AsyncAuctionListingMgr::GetList().push_back( (*itr) ); - AsyncAuctionListingMgr::GetTempList().clear(); - } - - for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr) - { - if ((*itr)._msTimer <= diff) - (*itr)._msTimer = 0; - else - (*itr)._msTimer -= diff; - } - - for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr) - if ((*itr)._msTimer == 0) - { - if ((*itr).Execute()) - AsyncAuctionListingMgr::GetList().erase(itr); - break; - } - } - } - Acore::Thread::Sleep(1); - } - LOG_INFO("auctionHouse", "Auction House Listing thread exiting without problems."); -} diff --git a/src/server/worldserver/WorldThread/WorldRunnable.h b/src/server/worldserver/WorldThread/WorldRunnable.h deleted file mode 100644 index 742ec02618..0000000000 --- a/src/server/worldserver/WorldThread/WorldRunnable.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> - */ - -/// \addtogroup Trinityd -/// @{ -/// \file - -#ifndef __WORLDRUNNABLE_H -#define __WORLDRUNNABLE_H - -#include "Threading.h" - -/// Heartbeat thread for the World -class WorldRunnable : public Acore::Runnable -{ -public: - void run() override; -}; - -class AuctionListingRunnable : public Acore::Runnable -{ -public: - void run() override; -}; -#endif -/// @} diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 9bbdf6fe33..954bc665e7 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -198,6 +198,19 @@ SourceDirectory = "" MySQLExecutable = "" # +# ThreadPool +# Description: Number of threads to be used for the global thread pool +# The thread pool is currently used for: +# - Signal handling +# - Remote access +# - Database keep-alive ping +# - Core freeze check +# - World socket networking +# Default: 2 + +ThreadPool = 2 + +# # IPLocationFile # Description: The path to your IP2Location database CSV file. # Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV" diff --git a/src/test/mocks/WorldMock.h b/src/test/mocks/WorldMock.h index 15c2c8cd1b..d58f0d16f8 100644 --- a/src/test/mocks/WorldMock.h +++ b/src/test/mocks/WorldMock.h @@ -13,7 +13,6 @@ #pragma GCC diagnostic ignored "-Wunused-parameter" void AddScripts() {} -bool ArenaSpectator::HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args) { return false; } class WorldMock: public IWorld { public: |