summaryrefslogtreecommitdiff
path: root/src/server/apps/authserver
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/apps/authserver')
-rw-r--r--src/server/apps/authserver/Authentication/AuthCodes.cpp39
-rw-r--r--src/server/apps/authserver/Authentication/AuthCodes.h91
-rw-r--r--src/server/apps/authserver/Main.cpp298
-rw-r--r--src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h22
-rw-r--r--src/server/apps/authserver/Server/AuthSession.cpp879
-rw-r--r--src/server/apps/authserver/Server/AuthSession.h121
-rw-r--r--src/server/apps/authserver/Server/AuthSocketMgr.h58
-rw-r--r--src/server/apps/authserver/authserver.conf.dist423
-rw-r--r--src/server/apps/authserver/authserver.icobin0 -> 85182 bytes
-rw-r--r--src/server/apps/authserver/authserver.rc93
-rw-r--r--src/server/apps/authserver/resource.h15
11 files changed, 2039 insertions, 0 deletions
diff --git a/src/server/apps/authserver/Authentication/AuthCodes.cpp b/src/server/apps/authserver/Authentication/AuthCodes.cpp
new file mode 100644
index 0000000000..0f7e5d0e09
--- /dev/null
+++ b/src/server/apps/authserver/Authentication/AuthCodes.cpp
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "AuthCodes.h"
+#include "RealmList.h"
+
+namespace AuthHelper
+{
+ constexpr static uint32 MAX_PRE_BC_CLIENT_BUILD = 6141;
+
+ bool IsPreBCAcceptedClientBuild(uint32 build)
+ {
+ return build <= MAX_PRE_BC_CLIENT_BUILD && sRealmList->GetBuildInfo(build);
+ }
+
+ bool IsPostBCAcceptedClientBuild(uint32 build)
+ {
+ return build > MAX_PRE_BC_CLIENT_BUILD && sRealmList->GetBuildInfo(build);
+ }
+
+ bool IsAcceptedClientBuild(uint32 build)
+ {
+ return sRealmList->GetBuildInfo(build) != nullptr;
+ }
+};
diff --git a/src/server/apps/authserver/Authentication/AuthCodes.h b/src/server/apps/authserver/Authentication/AuthCodes.h
new file mode 100644
index 0000000000..35037e0a3b
--- /dev/null
+++ b/src/server/apps/authserver/Authentication/AuthCodes.h
@@ -0,0 +1,91 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _AUTHCODES_H
+#define _AUTHCODES_H
+
+#include "Define.h"
+#include <array>
+
+enum AuthResult
+{
+ WOW_SUCCESS = 0x00,
+ WOW_FAIL_BANNED = 0x03,
+ WOW_FAIL_UNKNOWN_ACCOUNT = 0x04,
+ WOW_FAIL_INCORRECT_PASSWORD = 0x05,
+ WOW_FAIL_ALREADY_ONLINE = 0x06,
+ WOW_FAIL_NO_TIME = 0x07,
+ WOW_FAIL_DB_BUSY = 0x08,
+ WOW_FAIL_VERSION_INVALID = 0x09,
+ WOW_FAIL_VERSION_UPDATE = 0x0A,
+ WOW_FAIL_INVALID_SERVER = 0x0B,
+ WOW_FAIL_SUSPENDED = 0x0C,
+ WOW_FAIL_FAIL_NOACCESS = 0x0D,
+ WOW_SUCCESS_SURVEY = 0x0E,
+ WOW_FAIL_PARENTCONTROL = 0x0F,
+ WOW_FAIL_LOCKED_ENFORCED = 0x10,
+ WOW_FAIL_TRIAL_ENDED = 0x11,
+ WOW_FAIL_USE_BATTLENET = 0x12,
+ WOW_FAIL_ANTI_INDULGENCE = 0x13,
+ WOW_FAIL_EXPIRED = 0x14,
+ WOW_FAIL_NO_GAME_ACCOUNT = 0x15,
+ WOW_FAIL_CHARGEBACK = 0x16,
+ WOW_FAIL_INTERNET_GAME_ROOM_WITHOUT_BNET = 0x17,
+ WOW_FAIL_GAME_ACCOUNT_LOCKED = 0x18,
+ WOW_FAIL_UNLOCKABLE_LOCK = 0x19,
+ WOW_FAIL_CONVERSION_REQUIRED = 0x20,
+ WOW_FAIL_DISCONNECTED = 0xFF
+};
+
+enum LoginResult
+{
+ LOGIN_OK = 0x00,
+ LOGIN_FAILED = 0x01,
+ LOGIN_FAILED2 = 0x02,
+ LOGIN_BANNED = 0x03,
+ LOGIN_UNKNOWN_ACCOUNT = 0x04,
+ LOGIN_UNKNOWN_ACCOUNT3 = 0x05,
+ LOGIN_ALREADYONLINE = 0x06,
+ LOGIN_NOTIME = 0x07,
+ LOGIN_DBBUSY = 0x08,
+ LOGIN_BADVERSION = 0x09,
+ LOGIN_DOWNLOAD_FILE = 0x0A,
+ LOGIN_FAILED3 = 0x0B,
+ LOGIN_SUSPENDED = 0x0C,
+ LOGIN_FAILED4 = 0x0D,
+ LOGIN_CONNECTED = 0x0E,
+ LOGIN_PARENTALCONTROL = 0x0F,
+ LOGIN_LOCKED_ENFORCED = 0x10
+};
+
+enum ExpansionFlags
+{
+ POST_BC_EXP_FLAG = 0x2,
+ PRE_BC_EXP_FLAG = 0x1,
+ NO_VALID_EXP_FLAG = 0x0
+};
+
+struct RealmBuildInfo;
+
+namespace AuthHelper
+{
+ bool IsAcceptedClientBuild(uint32 build);
+ bool IsPostBCAcceptedClientBuild(uint32 build);
+ bool IsPreBCAcceptedClientBuild(uint32 build);
+};
+
+#endif
diff --git a/src/server/apps/authserver/Main.cpp b/src/server/apps/authserver/Main.cpp
new file mode 100644
index 0000000000..b0105d658b
--- /dev/null
+++ b/src/server/apps/authserver/Main.cpp
@@ -0,0 +1,298 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+* @file main.cpp
+* @brief Authentication Server main program
+*
+* This file contains the main program for the
+* authentication server
+*/
+
+#include "AppenderDB.h"
+#include "AuthSocketMgr.h"
+#include "Banner.h"
+#include "Config.h"
+#include "DatabaseEnv.h"
+#include "DatabaseLoader.h"
+#include "DeadlineTimer.h"
+#include "GitRevision.h"
+#include "IPLocation.h"
+#include "IoContext.h"
+#include "Log.h"
+#include "MySQLThreading.h"
+#include "ProcessPriority.h"
+#include "RealmList.h"
+#include "SecretMgr.h"
+#include "SharedDefines.h"
+#include "Util.h"
+#include <boost/asio/signal_set.hpp>
+#include <boost/program_options.hpp>
+#include <boost/version.hpp>
+#include <csignal>
+#include <filesystem>
+#include <iostream>
+#include <openssl/crypto.h>
+#include <openssl/opensslv.h>
+
+#ifndef _ACORE_REALM_CONFIG
+#define _ACORE_REALM_CONFIG "authserver.conf"
+#endif
+
+using boost::asio::ip::tcp;
+using namespace boost::program_options;
+namespace fs = std::filesystem;
+
+bool StartDB();
+void StopDB();
+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);
+variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile);
+
+/// Launch the auth server
+int main(int argc, char** argv)
+{
+ Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_AUTHSERVER;
+ signal(SIGABRT, &Acore::AbortHandler);
+
+ // Command line parsing
+ auto configFile = fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG));
+ auto vm = GetConsoleArguments(argc, argv, configFile);
+
+ // exit if help or version is enabled
+ if (vm.count("help"))
+ return 0;
+
+ // Add file and args in config
+ sConfigMgr->Configure(configFile.generic_string(), std::vector<std::string>(argv, argv + argc));
+
+ if (!sConfigMgr->LoadAppConfigs())
+ return 1;
+
+ // Init logging
+ sLog->RegisterAppender<AppenderDB>();
+ sLog->Initialize(nullptr);
+
+ Acore::Banner::Show("authserver",
+ [](std::string_view text)
+ {
+ LOG_INFO("server.authserver", text);
+ },
+ []()
+ {
+ LOG_INFO("server.authserver", "> Using configuration file {}", sConfigMgr->GetFilename());
+ LOG_INFO("server.authserver", "> Using SSL version: {} (library: {})", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
+ LOG_INFO("server.authserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
+ });
+
+ // authserver PID file creation
+ std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
+ if (!pidFile.empty())
+ {
+ if (uint32 pid = CreatePIDFile(pidFile))
+ LOG_INFO("server.authserver", "Daemon PID: {}\n", pid); // outError for red color in console
+ else
+ {
+ LOG_ERROR("server.authserver", "Cannot create PID file {} (possible error: permission)\n", pidFile);
+ return 1;
+ }
+ }
+
+ // Initialize the database connection
+ if (!StartDB())
+ return 1;
+
+ sSecretMgr->Initialize();
+
+ // 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(*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;
+ }
+
+ // Stop auth server if dry run
+ if (sConfigMgr->isDryRun())
+ {
+ LOG_INFO("server.authserver", "Dry run completed, terminating.");
+ return 0;
+ }
+
+ // Start the listening port (acceptor) for auth connections
+ int32 port = sConfigMgr->GetOption<int32>("RealmServerPort", 3724);
+ if (port < 0 || port > 0xFFFF)
+ {
+ LOG_ERROR("server.authserver", "Specified port out of allowed range (1-65535)");
+ return 1;
+ }
+
+ std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
+
+ if (!sAuthSocketMgr.StartNetwork(*ioContext, bindIp, port))
+ {
+ LOG_ERROR("server.authserver", "Failed to initialize network");
+ return 1;
+ }
+
+ std::shared_ptr<void> sAuthSocketMgrHandle(nullptr, [](void*) { sAuthSocketMgr.StopNetwork(); });
+
+ // Set signal handlers
+ boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM);
+#if AC_PLATFORM == AC_PLATFORM_WINDOWS
+ 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));
+
+ // 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));
+
+ 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));
+
+ // Start the io service worker loop
+ ioContext->run();
+
+ banExpiryCheckTimer->cancel();
+ dbPingTimer->cancel();
+
+ LOG_INFO("server.authserver", "Halting process...");
+
+ signals.cancel();
+
+ return 0;
+}
+
+/// Initialize connection to the database
+bool StartDB()
+{
+ MySQL::Library_Init();
+
+ // Load databases
+ // NOTE: While authserver is singlethreaded you should keep synch_threads == 1.
+ // Increasing it is just silly since only 1 will be used ever.
+ DatabaseLoader loader("server.authserver");
+ loader
+ .AddDatabase(LoginDatabase, "Login");
+
+ if (!loader.Load())
+ return false;
+
+ LOG_INFO("server.authserver", "Started auth database connection pool.");
+ sLog->SetRealmId(0); // Enables DB appenders when realm is set.
+ return true;
+}
+
+/// Close the connection to the database
+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));
+ }
+ }
+}
+
+variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile)
+{
+ options_description all("Allowed options");
+ all.add_options()
+ ("help,h", "print usage message")
+ ("version,v", "print version build info")
+ ("dry-run,d", "Dry run")
+ ("config,c", value<fs::path>(&configFile)->default_value(fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG))), "use <arg> as configuration file");
+
+ variables_map variablesMap;
+
+ try
+ {
+ store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), variablesMap);
+ notify(variablesMap);
+ }
+ catch (std::exception const& e)
+ {
+ std::cerr << e.what() << "\n";
+ }
+
+ if (variablesMap.count("help"))
+ {
+ std::cout << all << "\n";
+ }
+ else if (variablesMap.count("dry-run"))
+ {
+ sConfigMgr->setDryRun(true);
+ }
+
+ return variablesMap;
+}
diff --git a/src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h b/src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h
new file mode 100644
index 0000000000..e252912e7c
--- /dev/null
+++ b/src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h
@@ -0,0 +1,22 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Common.h"
+#include "Config.h"
+#include "DatabaseEnv.h"
+#include "Log.h"
+#include "RealmList.h"
diff --git a/src/server/apps/authserver/Server/AuthSession.cpp b/src/server/apps/authserver/Server/AuthSession.cpp
new file mode 100644
index 0000000000..ca411fa292
--- /dev/null
+++ b/src/server/apps/authserver/Server/AuthSession.cpp
@@ -0,0 +1,879 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "AuthSession.h"
+#include "AES.h"
+#include "AuthCodes.h"
+#include "Config.h"
+#include "CryptoGenerics.h"
+#include "CryptoHash.h"
+#include "CryptoRandom.h"
+#include "DatabaseEnv.h"
+#include "Errors.h"
+#include "IPLocation.h"
+#include "Log.h"
+#include "RealmList.h"
+#include "SecretMgr.h"
+#include "TOTP.h"
+#include "Timer.h"
+#include "Util.h"
+#include "StringConvert.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].Get<uint32>();
+ Login = fields[1].Get<std::string>();
+ IsLockedToIP = fields[2].Get<bool>();
+ LockCountry = fields[3].Get<std::string>();
+ LastIP = fields[4].Get<std::string>();
+ FailedLogins = fields[5].Get<uint32>();
+ IsBanned = fields[6].Get<bool>() || fields[8].Get<bool>();
+ IsPermanentlyBanned = fields[7].Get<bool>() || fields[9].Get<bool>();
+ SecurityLevel = static_cast<AccountTypes>(fields[10].Get<uint8>()) > SEC_CONSOLE ? SEC_CONSOLE : static_cast<AccountTypes>(fields[10].Get<uint8>());
+
+ // 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 {}", ip_address);
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
+ stmt->SetData(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].Get<uint64>() != 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 '{}:{}' tries to login!", GetRemoteIpAddress().to_string(), 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] '{}'", login);
+
+ _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->SetData(0, GetRemoteIpAddress().to_string());
+ stmt->SetData(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 '{}' is locked to IP - '{}' is logging in from '{}'", _accountInfo.Login, _accountInfo.LastIP, ipAddress);
+ 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 '{}' is not locked to ip", _accountInfo.Login);
+ if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00")
+ LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to country", _accountInfo.Login);
+ else if (!_ipCountry.empty())
+ {
+ LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to country: '{}' Player country is '{}'", _accountInfo.Login, _accountInfo.LockCountry, _ipCountry);
+ 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", "'{}:{}' [AuthChallenge] Banned account {} tried to login!", ipAddress, port, _accountInfo.Login);
+ return;
+ }
+ else
+ {
+ pkt << uint8(WOW_FAIL_SUSPENDED);
+ SendPacket(pkt);
+ LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Temporarily banned account {} tried to login!", ipAddress, port, _accountInfo.Login);
+ return;
+ }
+ }
+
+ uint8 securityFlags = 0;
+
+ // Check if a TOTP token is needed
+ if (!fields[11].IsNull())
+ {
+ securityFlags = 4;
+ _totpSecret = fields[11].Get<Binary>();
+
+ 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 '{}' has invalid ciphertext for TOTP token key stored", _accountInfo.Login);
+ SendPacket(pkt);
+ return;
+ }
+ }
+ }
+
+ _srp6.emplace(_accountInfo.Login,
+ fields[12].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(),
+ fields[13].Get<Binary, 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", "'{}:{}' [AuthChallenge] account {} is using '{}' locale ({})",
+ ipAddress, port, _accountInfo.Login, _localizationName, 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 = *Acore::StringTo<uint32>(token);
+ 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", "'{}:{}' User '{}' successfully authenticated", GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login);
+
+ // 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
+
+ std::string address = sConfigMgr->GetOption<bool>("AllowLoggingIPAddressesInDatabase", true, true) ? GetRemoteIpAddress().to_string() : "0.0.0.0";
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF);
+ stmt->SetData(0, _sessionKey);
+ stmt->SetData(1, address);
+ stmt->SetData(2, GetLocaleByName(_localizationName));
+ stmt->SetData(3, _os);
+ stmt->SetData(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", "'{}:{}' [AuthChallenge] account {} tried to login with invalid password!",
+ GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login);
+
+ 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->SetData(0, _accountInfo.Id);
+ logstmt->SetData(1, GetRemoteIpAddress().to_string());
+ logstmt->SetData(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->SetData(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->SetData(0, _accountInfo.Id);
+ stmt->SetData(1, WrongPassBanTime);
+ LoginDatabase.Execute(stmt);
+
+ LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} got banned for '{}' seconds because it failed to authenticate '{}' times",
+ GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login, WrongPassBanTime, _accountInfo.FailedLogins);
+ }
+ else
+ {
+ stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
+ stmt->SetData(0, GetRemoteIpAddress().to_string());
+ stmt->SetData(1, WrongPassBanTime);
+ LoginDatabase.Execute(stmt);
+
+ LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] IP got banned for '{}' seconds because account {} failed to authenticate '{}' times",
+ GetRemoteIpAddress().to_string(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login, _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] '{}'", login);
+
+ _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->SetData(0, GetRemoteIpAddress().to_string());
+ stmt->SetData(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].Get<Binary, 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", "'{}:{}' [ERROR] user {} tried to login, but session is invalid.", GetRemoteIpAddress().to_string(),
+ GetRemotePort(), _accountInfo.Login);
+ return false;
+ }
+}
+
+bool AuthSession::HandleRealmList()
+{
+ LOG_DEBUG("server.authserver", "Entering _HandleRealmList");
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS);
+ stmt->SetData(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].Get<uint32>()] = fields[1].Get<uint8>();
+ } 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/apps/authserver/Server/AuthSession.h b/src/server/apps/authserver/Server/AuthSession.h
new file mode 100644
index 0000000000..4a333c8168
--- /dev/null
+++ b/src/server/apps/authserver/Server/AuthSession.h
@@ -0,0 +1,121 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 "QueryResult.h"
+#include "SRP6.h"
+#include "Socket.h"
+#include <boost/asio/ip/tcp.hpp>
+#include <memory>
+
+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/apps/authserver/Server/AuthSocketMgr.h b/src/server/apps/authserver/Server/AuthSocketMgr.h
new file mode 100644
index 0000000000..53f46ff92a
--- /dev/null
+++ b/src/server/apps/authserver/Server/AuthSocketMgr.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AuthSocketMgr_h__
+#define AuthSocketMgr_h__
+
+#include "AuthSession.h"
+#include "SocketMgr.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/apps/authserver/authserver.conf.dist b/src/server/apps/authserver/authserver.conf.dist
new file mode 100644
index 0000000000..923074c332
--- /dev/null
+++ b/src/server/apps/authserver/authserver.conf.dist
@@ -0,0 +1,423 @@
+###############################################
+# AzerothCore Auth Server configuration file #
+###############################################
+[authserver]
+
+###################################################################################################
+# SECTION INDEX
+#
+# EXAMPLE CONFIG
+# AUTH SERVER SETTINGS
+# MYSQL SETTINGS
+# CRYPTOGRAPHY
+# UPDATE SETTINGS
+# LOGGING SYSTEM SETTINGS
+#
+###################################################################################################
+
+###################################################################################################
+# EXAMPLE CONFIG
+#
+# Variable
+# Description: Brief description what the variable is doing.
+# Important: Annotation for important things about this variable.
+# Example: "Example, i.e. if the value is a string"
+# Default: 10 - (Enabled|Comment|Variable name in case of grouped config options)
+# 0 - (Disabled|Comment|Variable name in case of grouped config options)
+#
+# Note to developers:
+# - Copy this example to keep the formatting.
+# - Line breaks should be at column 100.
+###################################################################################################
+
+###################################################################################################
+# AUTH SERVER SETTINGS
+#
+# LogsDir
+# Description: Logs directory setting.
+# Important: LogsDir needs to be quoted, as the string might contain space characters.
+# Logs directory must exists, or log file creation will be disabled.
+# Example: "/home/youruser/azerothcore/logs"
+# Default: "" - (Log files will be stored in the current path)
+
+LogsDir = ""
+
+#
+# MaxPingTime
+# Description: Time (in minutes) between database pings.
+# Default: 30
+
+MaxPingTime = 30
+
+#
+# RealmServerPort
+# Description: TCP port to reach the auth server.
+# Default: 3724
+
+RealmServerPort = 3724
+
+#
+#
+# BindIP
+# Description: Bind auth server to IP/hostname
+# Default: "0.0.0.0" - (Bind to all IPs on the system)
+
+BindIP = "0.0.0.0"
+
+#
+# PidFile
+# Description: Auth server PID file.
+# Example: "./authserver.pid" - (Enabled)
+# Default: "" - (Disabled)
+
+PidFile = ""
+
+#
+# UseProcessors
+# Description: Processors mask for Windows and Linux based multi-processor systems.
+# Example: For a computer with 3 CPUs:
+# 1 - 1st CPU only
+# 2 - 2nd CPU only
+# 4 - 3rd CPU only
+# 6 - 2nd + 3rd CPUs, because "2 | 4" -> 6
+# Default: 0 - (Selected by OS)
+# 1+ - (Bit mask value of selected processors)
+
+UseProcessors = 0
+
+#
+# ProcessPriority
+# Description: Process priority setting for Windows and Linux based systems.
+# Details: On Linux, a nice value of -15 is used. (requires superuser). On Windows, process is set to HIGH class.
+# Default: 0 - (Normal)
+# 1 - (High)
+
+ProcessPriority = 0
+
+#
+# RealmsStateUpdateDelay
+# Description: Time (in seconds) between realm list updates.
+# Default: 20 - (Enabled)
+# 0 - (Disabled)
+
+RealmsStateUpdateDelay = 20
+
+#
+# WrongPass.MaxCount
+# Description: Number of login attempts with wrong password before the account or IP will be
+# banned.
+# Default: 0 - (Disabled)
+# 1+ - (Enabled)
+
+WrongPass.MaxCount = 0
+
+#
+# WrongPass.BanTime
+# Description: Time (in seconds) for banning account or IP for invalid login attempts.
+# Default: 600 - (10 minutes)
+# 0 - (Permanent ban)
+
+WrongPass.BanTime = 600
+
+#
+# WrongPass.BanType
+# Description: Ban type for invalid login attempts.
+# Default: 0 - (Ban IP)
+# 1 - (Ban Account)
+
+WrongPass.BanType = 0
+
+#
+# WrongPass.Logging
+# Description: Additionally log attempted wrong password logging
+# Default: 0 - (Disabled)
+# 1 - (Enabled)
+
+WrongPass.Logging = 0
+
+#
+# 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
+
+#
+# SourceDirectory
+# Description: The path to your AzerothCore source directory.
+# If the path is left empty, the built-in CMAKE_SOURCE_DIR is used.
+# Example: "../AzerothCore"
+# Default: ""
+#
+
+SourceDirectory = ""
+
+#
+# MySQLExecutable
+# Description: The path to your MySQL CLI binary.
+# If the path is left empty, built-in path from cmake is used.
+# Example: "C:/Program Files/MariaDB 10.5/bin/mysql.exe"
+# "C:/Program Files/MySQL/MySQL Server 5.6/bin/mysql.exe"
+# "mysql.exe"
+# "/usr/bin/mysql"
+# Default: ""
+#
+
+MySQLExecutable = ""
+
+#
+# IPLocationFile
+# Description: The path to your IP2Location database CSV file.
+# Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV"
+# "/home/acore/IP2LOCATION-LITE-DB1.CSV"
+# Default: "" - (Disabled)
+
+IPLocationFile = ""
+
+#
+# AllowLoggingIPAddressesInDatabase
+# Description: Specifies if IP addresses can be logged to the database
+# Default: 1 - (Enabled)
+# 0 - (Disabled)
+#
+
+AllowLoggingIPAddressesInDatabase = 1
+
+#
+###################################################################################################
+
+###################################################################################################
+# MYSQL SETTINGS
+#
+# LoginDatabaseInfo
+# Description: Database connection settings for the realm server.
+# Example: "hostname;port;username;password;database"
+# ".;somenumber;username;password;database" - (Use named pipes on Windows
+# "enable-named-pipe" to [mysqld]
+# section my.ini)
+# ".;/path/to/unix_socket;username;password;database" - (use Unix sockets on
+# Unix/Linux)
+# Default: "127.0.0.1;3306;acore;acore;acore_auth"
+
+LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth"
+
+#
+# Database.Reconnect.Seconds
+# Database.Reconnect.Attempts
+#
+# Description: How many seconds between every reconnection attempt
+# and how many attempts will be performed in total
+# Default: 20 attempts every 15 seconds
+#
+
+Database.Reconnect.Seconds = 15
+Database.Reconnect.Attempts = 20
+
+#
+# LoginDatabase.WorkerThreads
+# Description: The amount of worker threads spawned to handle asynchronous (delayed) MySQL
+# statements. Each worker thread is mirrored with its own connection to the
+# Default: 1
+
+LoginDatabase.WorkerThreads = 1
+
+#
+# LoginDatabase.SynchThreads
+# Description: The amount of MySQL connections spawned to handle.
+# Default: 1 - (LoginDatabase.WorkerThreads)
+#
+
+LoginDatabase.SynchThreads = 1
+
+#
+###################################################################################################
+
+###################################################################################################
+# CRYPTOGRAPHY
+
+#
+# EnableTOTP
+# Description: Check if a TOTP token is needed on account login
+#
+# Default: 0 - (Disabled)
+# 1 - (Enabled)
+
+EnableTOTP = 0
+
+# TOTPMasterSecret
+# Description: The master key used to encrypt TOTP secrets for database storage.
+# If you want to change this, uncomment TOTPOldMasterSecret, then copy
+# your old secret there and startup authserver once. Afterwards, you can re-
+# comment that line and get rid of your old secret.
+#
+# Default: <blank> - (Store TOTP secrets unencrypted)
+# Example: 000102030405060708090A0B0C0D0E0F
+
+TOTPMasterSecret =
+# TOTPOldMasterSecret =
+
+#
+###################################################################################################
+
+###################################################################################################
+# UPDATE SETTINGS
+#
+# Updates.EnableDatabases
+# Description: A mask that describes which databases shall be updated.
+#
+# Following flags are available
+# DATABASE_LOGIN = 1, // Auth database
+#
+# Default: 0 - (All Disabled)
+# 1 - (All Enabled)
+
+Updates.EnableDatabases = 1
+
+#
+# Updates.AutoSetup
+# Description: Auto populate empty databases.
+# Default: 1 - (Enabled)
+# 0 - (Disabled)
+
+Updates.AutoSetup = 1
+
+#
+# Updates.Redundancy
+# Description: Perform data redundancy checks through hashing
+# to detect changes on sql updates and reapply it.
+# Default: 1 - (Enabled)
+# 0 - (Disabled)
+
+Updates.Redundancy = 1
+
+#
+# Updates.ArchivedRedundancy
+# Description: Check hashes of archived updates (slows down startup).
+# Default: 0 - (Disabled)
+# 1 - (Enabled)
+
+Updates.ArchivedRedundancy = 0
+
+#
+# Updates.AllowRehash
+# Description: Inserts the current file hash in the database if it is left empty.
+# Useful if you want to mark a file as applied but you don't know its hash.
+# Default: 1 - (Enabled)
+# 0 - (Disabled)
+
+Updates.AllowRehash = 1
+
+#
+# Updates.CleanDeadRefMaxCount
+# Description: Cleans dead/ orphaned references that occur if an update was removed or renamed and edited in one step.
+# It only starts the clean up if the count of the missing updates is below or equal the Updates.CleanDeadRefMaxCount value.
+# This way prevents erasing of the update history due to wrong source directory state (maybe wrong branch or bad revision).
+# Disable this if you want to know if the database is in a possible "dirty state".
+# Default: 3 - (Enabled)
+# 0 - (Disabled)
+# -1 - (Enabled - unlimited)
+
+Updates.CleanDeadRefMaxCount = 3
+###################################################################################################
+
+###################################################################################################
+#
+# LOGGING SYSTEM SETTINGS
+#
+# Appender config values: Given an appender "name"
+# Appender.name
+# Description: Defines 'where to log'
+# Format: Type,LogLevel,Flags,optional1,optional2,optional3
+#
+# Type
+# 0 - (None)
+# 1 - (Console)
+# 2 - (File)
+# 3 - (DB)
+#
+# LogLevel
+# 0 - (Disabled)
+# 1 - (Fatal)
+# 2 - (Error)
+# 3 - (Warning)
+# 4 - (Info)
+# 5 - (Debug)
+# 6 - (Trace)
+#
+# Flags:
+# 0 - None
+# 1 - Prefix Timestamp to the text
+# 2 - Prefix Log Level to the text
+# 4 - Prefix Log Filter type to the text
+# 8 - Append timestamp to the log file name. Format: YYYY-MM-DD_HH-MM-SS (Only used with Type = 2)
+# 16 - Make a backup of existing file before overwrite (Only used with Mode = w)
+#
+# Colors (read as optional1 if Type = Console)
+# Format: "fatal error warn info debug trace"
+# 0 - BLACK
+# 1 - RED
+# 2 - GREEN
+# 3 - BROWN
+# 4 - BLUE
+# 5 - MAGENTA
+# 6 - CYAN
+# 7 - GREY
+# 8 - YELLOW
+# 9 - LRED
+# 10 - LGREEN
+# 11 - LBLUE
+# 12 - LMAGENTA
+# 13 - LCYAN
+# 14 - WHITE
+# Example: "1 9 3 6 5 8"
+#
+# File: Name of the file (read as optional1 if Type = File)
+# Allows to use one "%s" to create dynamic files
+#
+# Mode: Mode to open the file (read as optional2 if Type = File)
+# a - (Append)
+# w - (Overwrite)
+#
+# MaxFileSize: Maximum file size of the log file before creating a new log file
+# (read as optional3 if Type = File)
+# Size is measured in bytes expressed in a 64-bit unsigned integer.
+# Maximum value is 4294967295 (4 GB). Leave blank for no limit.
+# NOTE: Does not work with dynamic filenames.
+# Example: 536870912 (512 MB)
+#
+
+Appender.Console=1,5,0,"1 9 3 6 5 8"
+Appender.Auth=2,5,0,Auth.log,w
+
+# Logger config values: Given a logger "name"
+# Logger.name
+# Description: Defines 'What to log'
+# Format: LogLevel,AppenderList
+#
+# LogLevel
+# 0 - (Disabled)
+# 1 - (Fatal)
+# 2 - (Error)
+# 3 - (Warning)
+# 4 - (Info)
+# 5 - (Debug)
+# 6 - (Trace)
+#
+# AppenderList: List of appenders linked to logger
+# (Using spaces as separator).
+#
+
+Logger.root=4,Console Auth
+
+#
+###################################################################################################
diff --git a/src/server/apps/authserver/authserver.ico b/src/server/apps/authserver/authserver.ico
new file mode 100644
index 0000000000..2d500278c9
--- /dev/null
+++ b/src/server/apps/authserver/authserver.ico
Binary files differ
diff --git a/src/server/apps/authserver/authserver.rc b/src/server/apps/authserver/authserver.rc
new file mode 100644
index 0000000000..0b30740e63
--- /dev/null
+++ b/src/server/apps/authserver/authserver.rc
@@ -0,0 +1,93 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resource.h"
+#include "revision.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "windows.h" //"afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APPICON ICON "authserver.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+// Neutre (Par défaut système) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD)
+#ifdef _WIN32
+LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION VER_FILEVERSION
+PRODUCTVERSION VER_PRODUCTVERSION
+
+FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+
+#ifndef _DEBUG
+ FILEFLAGS 0
+#else
+ #define VER_PRERELEASE VS_FF_PRERELEASE
+ #define VER_PRIVATEBUILD VS_FF_PRIVATEBUILD
+ #define VER_DEBUG 0
+ FILEFLAGS (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG)
+#endif
+
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_APP
+
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "080004b0"
+ BEGIN
+ VALUE "CompanyName", VER_COMPANYNAME_STR
+ VALUE "FileDescription", "Authentication Server Daemon"
+ VALUE "FileVersion", VER_FILEVERSION_STR
+ VALUE "InternalName", "authserver"
+ VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR
+ VALUE "OriginalFilename", "authserver.exe"
+ VALUE "ProductName", "Authentication Server"
+ VALUE "ProductVersion", VER_PRODUCTVERSION_STR
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x800, 1200
+ END
+END
+#endif
diff --git a/src/server/apps/authserver/resource.h b/src/server/apps/authserver/resource.h
new file mode 100644
index 0000000000..5415d15d80
--- /dev/null
+++ b/src/server/apps/authserver/resource.h
@@ -0,0 +1,15 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by SunwellCore.rc
+//
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif