diff options
Diffstat (limited to 'src/server/apps/authserver')
-rw-r--r-- | src/server/apps/authserver/Authentication/AuthCodes.cpp | 39 | ||||
-rw-r--r-- | src/server/apps/authserver/Authentication/AuthCodes.h | 91 | ||||
-rw-r--r-- | src/server/apps/authserver/Main.cpp | 298 | ||||
-rw-r--r-- | src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h | 22 | ||||
-rw-r--r-- | src/server/apps/authserver/Server/AuthSession.cpp | 879 | ||||
-rw-r--r-- | src/server/apps/authserver/Server/AuthSession.h | 121 | ||||
-rw-r--r-- | src/server/apps/authserver/Server/AuthSocketMgr.h | 58 | ||||
-rw-r--r-- | src/server/apps/authserver/authserver.conf.dist | 423 | ||||
-rw-r--r-- | src/server/apps/authserver/authserver.ico | bin | 0 -> 85182 bytes | |||
-rw-r--r-- | src/server/apps/authserver/authserver.rc | 93 | ||||
-rw-r--r-- | src/server/apps/authserver/resource.h | 15 |
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 Binary files differnew file mode 100644 index 0000000000..2d500278c9 --- /dev/null +++ b/src/server/apps/authserver/authserver.ico 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 |