Merge branch 'master' into Playerbot

This commit is contained in:
郑佩茹
2022-05-31 09:11:39 -06:00
72 changed files with 1245 additions and 781 deletions

View File

@@ -0,0 +1,198 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# Make the script module list available in the current scope
GetApplicationsList(APPLICATIONS_BUILD_LIST)
if (APPS_BUILD STREQUAL "none")
set(APPS_DEFAULT_BUILD "disabled")
else()
set(APPS_DEFAULT_BUILD "enabled")
endif()
# Sets BUILD_APPS_USE_WHITELIST
# Sets BUILD_APPS_WHITELIST
if (APPS_BUILD MATCHES "-only")
set(BUILD_APPS_USE_WHITELIST ON)
if (APPS_BUILD STREQUAL "auth-only")
list(APPEND BUILD_APPS_WHITELIST authserver)
endif()
if (APPS_BUILD STREQUAL "world-only")
list(APPEND BUILD_APPS_WHITELIST worldserver)
endif()
endif()
# Set the SCRIPTS_${BUILD_APP} variables from the
# variables set above
foreach(BUILD_APP ${APPLICATIONS_BUILD_LIST})
ApplicationNameToVariable(${BUILD_APP} BUILD_APP_VARIABLE)
if(${BUILD_APP_VARIABLE} STREQUAL "default")
if(BUILD_APPS_USE_WHITELIST)
list(FIND BUILD_APPS_WHITELIST "${BUILD_APP}" INDEX)
if(${INDEX} GREATER -1)
set(${BUILD_APP_VARIABLE} ${APPS_DEFAULT_BUILD})
else()
set(${BUILD_APP_VARIABLE} "disabled")
endif()
else()
set(${BUILD_APP_VARIABLE} ${APPS_DEFAULT_BUILD})
endif()
endif()
# Build the Graph values
if(${BUILD_APP_VARIABLE} MATCHES "enabled")
list(APPEND BUILD_APP_GRAPH_KEYS apps)
set(BUILD_APP_VALUE_DISPLAY_apps apps)
list(APPEND BUILD_APP_VALUE_CONTAINS_apps ${BUILD_APP})
if (${BUILD_APP} MATCHES "authserver")
set (BUILD_APPLICATION_AUTHSERVER 1)
elseif(${BUILD_APP} MATCHES "worldserver")
set (BUILD_APPLICATION_WORLDSERVER 1)
endif()
else()
list(APPEND BUILD_APP_GRAPH_KEYS disabled)
set(BUILD_APP_VALUE_DISPLAY_disabled disabled)
list(APPEND BUILD_APP_VALUE_CONTAINS_disabled ${BUILD_APP})
endif()
endforeach()
list(SORT BUILD_APP_GRAPH_KEYS)
list(REMOVE_DUPLICATES BUILD_APP_GRAPH_KEYS)
# Display the graphs
message("")
message("* Apps build list (${APPS_BUILD}):")
message(" |")
foreach(BUILD_APP_GRAPH_KEY ${BUILD_APP_GRAPH_KEYS})
if(NOT BUILD_APP_GRAPH_KEY STREQUAL "disabled")
message(" +- ${BUILD_APP_VALUE_DISPLAY_${BUILD_APP_GRAPH_KEY}}")
else()
message(" | ${BUILD_APP_VALUE_DISPLAY_${BUILD_APP_GRAPH_KEY}}")
endif()
foreach(BUILD_APP_GRAPH_ENTRY ${BUILD_APP_VALUE_CONTAINS_${BUILD_APP_GRAPH_KEY}})
message(" | +- ${BUILD_APP_GRAPH_ENTRY}")
endforeach()
message(" |")
endforeach()
message("")
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
# Generates the actual apps projects
foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST})
GetPathToApplication(${APPLICATION_NAME} SOURCE_APP_PATH)
ApplicationNameToVariable(${APPLICATION_NAME} BUILD_APP_VARIABLE)
if (${BUILD_APP_VARIABLE} STREQUAL "disabled")
continue()
endif()
unset(APP_PRIVATE_SOURCES)
CollectSourceFiles(
${SOURCE_APP_PATH}
APP_PRIVATE_SOURCES
# Exclude
${SOURCE_APP_PATH}/PrecompiledHeaders)
if (WIN32)
list(APPEND APP_PRIVATE_SOURCES ${winDebugging})
if (${APPLICATION_NAME} MATCHES "worldserver")
list(APPEND APP_PRIVATE_SOURCES ${winService})
endif()
if (MSVC)
list(APPEND APP_PRIVATE_SOURCES ${SOURCE_APP_PATH}/${APPLICATION_NAME}.rc)
endif()
endif()
GetProjectNameOfApplicationName(${APPLICATION_NAME} APP_PROJECT_NAME)
# Create the application project
add_executable(${APP_PROJECT_NAME}
${APP_PRIVATE_SOURCES})
add_dependencies(${APP_PROJECT_NAME} revision.h)
target_link_libraries(${APP_PROJECT_NAME}
PRIVATE
acore-core-interface)
if (${APP_PROJECT_NAME} MATCHES "authserver")
target_link_libraries(${APP_PROJECT_NAME}
PUBLIC
shared)
elseif(${APP_PROJECT_NAME} MATCHES "worldserver")
target_link_libraries(${APP_PROJECT_NAME}
PUBLIC
modules
scripts
game
gsoap
readline
gperftools)
if (UNIX AND NOT NOJEM)
set(${APP_PROJECT_NAME}_LINK_FLAGS "-pthread -lncurses ${${APP_PROJECT_NAME}_LINK_FLAGS}")
endif()
set_target_properties(${APP_PROJECT_NAME} PROPERTIES LINK_FLAGS "${${APP_PROJECT_NAME}_LINK_FLAGS}")
# Add all dynamic projects as dependency to the worldserver
if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES)
add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES})
endif()
endif()
unset(APP_PUBLIC_INCLUDES)
CollectIncludeDirectories(
${SOURCE_APP_PATH}
APP_PUBLIC_INCLUDES
# Exclude
${SOURCE_APP_PATH}/PrecompiledHeaders)
target_include_directories(${APP_PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(${APP_PROJECT_NAME}
PUBLIC
${APP_PUBLIC_INCLUDES}
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_NAME})
set_target_properties(${APP_PROJECT_NAME}
PROPERTIES
FOLDER
"server")
# Install config
CopyApplicationConfig(${APP_PROJECT_NAME} ${APPLICATION_NAME})
if (UNIX)
install(TARGETS ${APP_PROJECT_NAME} DESTINATION bin)
elseif (WIN32)
install(TARGETS ${APP_PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_PREFIX}")
endif()
set(PATH_TO_PCH ${SOURCE_APP_PATH}/PrecompiledHeaders/${APPLICATION_NAME}PCH.h)
# Generate precompiled header
if (USE_COREPCH AND EXISTS ${PATH_TO_PCH})
add_cxx_pch(${APP_PROJECT_NAME} ${PATH_TO_PCH})
endif()
endforeach()

View File

@@ -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;
}
};

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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__

View File

@@ -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
#
###################################################################################################

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,149 @@
/*
* 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 "ACSoap.h"
#include "AccountMgr.h"
#include "Log.h"
#include "World.h"
#include "soapStub.h"
void ACSoapThread(const std::string& host, uint16 port)
{
struct soap soap;
soap_init(&soap);
soap_set_imode(&soap, SOAP_C_UTFSTRING);
soap_set_omode(&soap, SOAP_C_UTFSTRING);
// check every 3 seconds if world ended
soap.accept_timeout = 3;
soap.recv_timeout = 5;
soap.send_timeout = 5;
if (!soap_valid_socket(soap_bind(&soap, host.c_str(), port, 100)))
{
LOG_ERROR("network.soap", "ACSoap: couldn't bind to {}:{}", host, port);
exit(-1);
}
LOG_INFO("network.soap", "ACSoap: bound to http://{}:{}", host, port);
while (!World::IsStopped())
{
if (!soap_valid_socket(soap_accept(&soap)))
continue; // ran into an accept timeout
LOG_DEBUG("network.soap", "ACSoap: accepted connection from IP={}.{}.{}.{}", (int)(soap.ip >> 24) & 0xFF, (int)(soap.ip >> 16) & 0xFF, (int)(soap.ip >> 8) & 0xFF, (int)soap.ip & 0xFF);
struct soap* thread_soap = soap_copy(&soap);// make a safe copy
process_message(thread_soap);
}
soap_destroy(&soap);
soap_end(&soap);
soap_done(&soap);
}
void process_message(struct soap* soap_message)
{
LOG_TRACE("network.soap", "SOAPWorkingThread::process_message");
soap_serve(soap_message);
soap_destroy(soap_message); // dealloc C++ data
soap_end(soap_message); // dealloc data and clean up
soap_free(soap_message); // detach soap struct and fre up the memory
}
/*
Code used for generating stubs:
int ns1__executeCommand(char* command, char** result);
*/
int ns1__executeCommand(soap* soap, char* command, char** result)
{
// security check
if (!soap->userid || !soap->passwd)
{
LOG_DEBUG("network.soap", "ACSoap: Client didn't provide login information");
return 401;
}
uint32 accountId = AccountMgr::GetId(soap->userid);
if (!accountId)
{
LOG_DEBUG("network", "ACSoap: Client used invalid username '{}'", soap->userid);
return 401;
}
if (!AccountMgr::CheckPassword(accountId, soap->passwd))
{
LOG_DEBUG("network.soap", "ACSoap: invalid password for account '{}'", soap->userid);
return 401;
}
if (AccountMgr::GetSecurity(accountId) < SEC_ADMINISTRATOR)
{
LOG_DEBUG("network.soap", "ACSoap: {}'s gmlevel is too low", soap->userid);
return 403;
}
if (!command || !*command)
return soap_sender_fault(soap, "Command can not be empty", "The supplied command was an empty string");
LOG_DEBUG("network.soap", "ACSoap: got command '{}'", command);
SOAPCommand connection;
// commands are executed in the world thread. We have to wait for them to be completed
{
// CliCommandHolder will be deleted from world, accessing after queueing is NOT save
CliCommandHolder* cmd = new CliCommandHolder(&connection, command, &SOAPCommand::print, &SOAPCommand::commandFinished);
sWorld->QueueCliCommand(cmd);
}
// Wait until the command has finished executing
connection.finishedPromise.get_future().wait();
// The command has finished executing already
char* printBuffer = soap_strdup(soap, connection.m_printBuffer.c_str());
if (connection.hasCommandSucceeded())
{
*result = printBuffer;
return SOAP_OK;
}
else
return soap_sender_fault(soap, printBuffer, printBuffer);
}
void SOAPCommand::commandFinished(void* soapconnection, bool success)
{
SOAPCommand* con = (SOAPCommand*)soapconnection;
con->setCommandSuccess(success);
}
////////////////////////////////////////////////////////////////////////////////
//
// Namespace Definition Table
//
////////////////////////////////////////////////////////////////////////////////
struct Namespace namespaces[] =
{
{ "SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", nullptr, nullptr }, // must be first
{ "SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", nullptr, nullptr }, // must be second
{ "xsi", "http://www.w3.org/1999/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", nullptr },
{ "xsd", "http://www.w3.org/1999/XMLSchema", "http://www.w3.org/*/XMLSchema", nullptr },
{ "ns1", "urn:AC", nullptr, nullptr }, // "ns1" namespace prefix
{ nullptr, nullptr, nullptr, nullptr }
};

View File

@@ -0,0 +1,64 @@
/*
* 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 _ACSOAP_H
#define _ACSOAP_H
#include "Define.h"
#include <future>
#include <mutex>
void process_message(struct soap* soap_message);
void ACSoapThread(const std::string& host, uint16 port);
class SOAPCommand
{
public:
SOAPCommand() :
m_success(false) { }
~SOAPCommand() { }
void appendToPrintBuffer(std::string_view msg)
{
m_printBuffer += msg;
}
void setCommandSuccess(bool val)
{
m_success = val;
finishedPromise.set_value();
}
bool hasCommandSucceeded() const
{
return m_success;
}
static void print(void* callbackArg, std::string_view msg)
{
((SOAPCommand*)callbackArg)->appendToPrintBuffer(msg);
}
static void commandFinished(void* callbackArg, bool success);
bool m_success;
std::string m_printBuffer;
std::promise<void> finishedPromise;
};
#endif

View File

@@ -0,0 +1,194 @@
/*
* 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/>.
*/
/// \addtogroup Acored
/// @{
/// \file
#include "CliRunnable.h"
#include "Config.h"
#include "Errors.h"
#include "ObjectMgr.h"
#include "World.h"
#include <fmt/core.h>
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
#include "Chat.h"
#include "ChatCommand.h"
#include <cstring>
#include <readline/history.h>
#include <readline/readline.h>
#endif
static constexpr char CLI_PREFIX[] = "AC> ";
static inline void PrintCliPrefix()
{
fmt::print(CLI_PREFIX);
}
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
namespace Acore::Impl::Readline
{
static std::vector<std::string> vec;
char* cli_unpack_vector(char const*, int state)
{
static size_t i=0;
if (!state)
i = 0;
if (i < vec.size())
return strdup(vec[i++].c_str());
else
return nullptr;
}
char** cli_completion(char const* text, int /*start*/, int /*end*/)
{
::rl_attempted_completion_over = 1;
vec = Acore::ChatCommands::GetAutoCompletionsFor(CliHandler(nullptr,nullptr), text);
return ::rl_completion_matches(text, &cli_unpack_vector);
}
int cli_hook_func()
{
if (World::IsStopped())
::rl_done = 1;
return 0;
}
}
#endif
void utf8print(void* /*arg*/, std::string_view str)
{
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
fmt::print(str);
#else
{
fmt::print(str);
fflush(stdout);
}
#endif
}
void commandFinished(void*, bool /*success*/)
{
PrintCliPrefix();
fflush(stdout);
}
#ifdef linux
// Non-blocking keypress detector, when return pressed, return 1, else always return 0
int kb_hit_return()
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, nullptr, nullptr, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
}
#endif
/// %Thread start
void CliThread()
{
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
// print this here the first time
// later it will be printed after command queue updates
PrintCliPrefix();
#else
::rl_attempted_completion_function = &Acore::Impl::Readline::cli_completion;
{
static char BLANK = '\0';
::rl_completer_word_break_characters = &BLANK;
}
::rl_event_hook = &Acore::Impl::Readline::cli_hook_func;
#endif
if (sConfigMgr->GetOption<bool>("BeepAtStart", true))
printf("\a"); // \a = Alert
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
if (sConfigMgr->GetOption<bool>("FlashAtStart", true))
{
FLASHWINFO fInfo;
fInfo.cbSize = sizeof(FLASHWINFO);
fInfo.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG;
fInfo.hwnd = GetConsoleWindow();
fInfo.uCount = 0;
fInfo.dwTimeout = 0;
FlashWindowEx(&fInfo);
}
#endif
///- As long as the World is running (no World::m_stopEvent), get the command line and handle it
while (!World::IsStopped())
{
fflush(stdout);
std::string command;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
wchar_t commandbuf[256];
if (fgetws(commandbuf, sizeof(commandbuf), stdin))
{
if (!WStrToUtf8(commandbuf, wcslen(commandbuf), command))
{
PrintCliPrefix();
continue;
}
}
#else
char* command_str = readline(CLI_PREFIX);
::rl_bind_key('\t', ::rl_complete);
if (command_str != nullptr)
{
command = command_str;
free(command_str);
}
#endif
if (!command.empty())
{
std::size_t nextLineIndex = command.find_first_of("\r\n");
if (nextLineIndex != std::string::npos)
{
if (nextLineIndex == 0)
{
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
PrintCliPrefix();
#endif
continue;
}
command.erase(nextLineIndex);
}
fflush(stdout);
sWorld->QueueCliCommand(new CliCommandHolder(nullptr, command.c_str(), &utf8print, &commandFinished));
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
add_history(command.c_str());
#endif
}
else if (feof(stdin))
{
World::StopNow(SHUTDOWN_EXIT_CODE);
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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/>.
*/
/// \addtogroup Acored
/// @{
/// \file
#ifndef __CLIRUNNABLE_H
#define __CLIRUNNABLE_H
/// Command Line Interface handling thread
void CliThread();
#endif
/// @}

View File

@@ -0,0 +1,773 @@
/*
* 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/>.
*/
/// \addtogroup Acored Acore Daemon
/// @{
/// \file
#include "ACSoap.h"
#include "AppenderDB.h"
#include "AsyncAcceptor.h"
#include "AsyncAuctionListing.h"
#include "Banner.h"
#include "BattlegroundMgr.h"
#include "BigNumber.h"
#include "CliRunnable.h"
#include "Common.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "DeadlineTimer.h"
#include "GitRevision.h"
#include "IoContext.h"
#include "MapMgr.h"
#include "Metric.h"
#include "ModuleMgr.h"
#include "ModulesScriptLoader.h"
#include "MySQLThreading.h"
#include "OpenSSLCrypto.h"
#include "OutdoorPvPMgr.h"
#include "ProcessPriority.h"
#include "RASession.h"
#include "RealmList.h"
#include "Resolver.h"
#include "ScriptLoader.h"
#include "ScriptMgr.h"
#include "SecretMgr.h"
#include "SharedDefines.h"
#include "World.h"
#include "WorldSocket.h"
#include "WorldSocketMgr.h"
#include <boost/asio/signal_set.hpp>
#include <boost/program_options.hpp>
#include <csignal>
#include <filesystem>
#include <iostream>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
#include "ServiceWin32.h"
char serviceName[] = "worldserver";
char serviceLongName[] = "AzerothCore world service";
char serviceDescription[] = "AzerothCore World of Warcraft emulator world service";
/*
* -1 - not in service mode
* 0 - stopped
* 1 - running
* 2 - paused
*/
int m_ServiceStatus = -1;
#endif
#ifndef _ACORE_CORE_CONFIG
#define _ACORE_CORE_CONFIG "worldserver.conf"
#endif
#define WORLD_SLEEP_CONST 10
using namespace boost::program_options;
namespace fs = std::filesystem;
class FreezeDetector
{
public:
FreezeDetector(Acore::Asio::IoContext& ioContext, uint32 maxCoreStuckTime)
: _timer(ioContext), _worldLoopCounter(0), _lastChangeMsTime(getMSTime()), _maxCoreStuckTimeInMs(maxCoreStuckTime) { }
static void Start(std::shared_ptr<FreezeDetector> const& freezeDetector)
{
freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(5));
freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, std::weak_ptr<FreezeDetector>(freezeDetector), std::placeholders::_1));
}
static void Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error);
private:
Acore::Asio::DeadlineTimer _timer;
uint32 _worldLoopCounter;
uint32 _lastChangeMsTime;
uint32 _maxCoreStuckTimeInMs;
};
void SignalHandler(boost::system::error_code const& error, int signalNumber);
void ClearOnlineAccounts();
bool StartDB();
void StopDB();
bool LoadRealmInfo(Acore::Asio::IoContext& ioContext);
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext);
void ShutdownCLIThread(std::thread* cliThread);
void AuctionListingRunnable();
void ShutdownAuctionListingThread(std::thread* thread);
void WorldUpdateLoop();
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& cfg_service);
/// Launch the Azeroth server
int main(int argc, char** argv)
{
Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_WORLDSERVER;
signal(SIGABRT, &Acore::AbortHandler);
// Command line parsing
auto configFile = fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG));
std::string configService;
auto vm = GetConsoleArguments(argc, argv, configFile, configService);
// exit if help or version is enabled
if (vm.count("help"))
return 0;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
if (configService.compare("install") == 0)
return WinServiceInstall() == true ? 0 : 1;
else if (configService.compare("uninstall") == 0)
return WinServiceUninstall() == true ? 0 : 1;
else if (configService.compare("run") == 0)
WinServiceRun();
#endif
// Add file and args in config
sConfigMgr->Configure(configFile.generic_string(), {argv, argv + argc}, CONFIG_FILE_LIST);
if (!sConfigMgr->LoadAppConfigs())
return 1;
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
// Init all logs
sLog->RegisterAppender<AppenderDB>();
// If logs are supposed to be handled async then we need to pass the IoContext into the Log singleton
sLog->Initialize(sConfigMgr->GetOption<bool>("Log.Async.Enable", false) ? ioContext.get() : nullptr);
Acore::Banner::Show("worldserver-daemon",
[](std::string_view text)
{
LOG_INFO("server.worldserver", text);
},
[]()
{
LOG_INFO("server.worldserver", "> Using configuration file {}", sConfigMgr->GetFilename());
LOG_INFO("server.worldserver", "> Using SSL version: {} (library: {})", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
LOG_INFO("server.worldserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
});
OpenSSLCrypto::threadsSetup();
std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); });
// Seed the OpenSSL's PRNG here.
// That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login
BigNumber seed;
seed.SetRand(16 * 8);
/// worldserver PID file creation
std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
if (!pidFile.empty())
{
if (uint32 pid = CreatePIDFile(pidFile))
LOG_ERROR("server", "Daemon PID: {}\n", pid); // outError for red color in console
else
{
LOG_ERROR("server", "Cannot create PID file {} (possible error: permission)\n", pidFile);
return 1;
}
}
// Set signal handlers (this must be done before starting IoContext threads, because otherwise they would unblock and exit)
boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
signals.add(SIGBREAK);
#endif
signals.async_wait(SignalHandler);
// Start the Boost based thread pool
int numThreads = sConfigMgr->GetOption<int32>("ThreadPool", 1);
std::shared_ptr<std::vector<std::thread>> threadPool(new std::vector<std::thread>(), [ioContext](std::vector<std::thread>* del)
{
ioContext->stop();
for (std::thread& thr : *del)
thr.join();
delete del;
});
if (numThreads < 1)
{
numThreads = 1;
}
for (int i = 0; i < numThreads; ++i)
{
threadPool->push_back(std::thread([ioContext]()
{
ioContext->run();
}));
}
// Set process priority according to configuration settings
SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false));
// Loading modules configs before scripts
sConfigMgr->LoadModulesConfigs();
sScriptMgr->SetScriptLoader(AddScripts);
sScriptMgr->SetModulesLoader(AddModulesScripts);
std::shared_ptr<void> sScriptMgrHandle(nullptr, [](void*)
{
sScriptMgr->Unload();
//sScriptReloadMgr->Unload();
});
LOG_INFO("server.loading", "Initializing Scripts...");
sScriptMgr->Initialize();
// Start the databases
if (!StartDB())
return 1;
std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); });
// set server offline (not connectable)
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = (flag & ~{}) | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
LoadRealmInfo(*ioContext);
sMetric->Initialize(realm.Name, *ioContext, []()
{
METRIC_VALUE("online_players", sWorld->GetPlayerCount());
METRIC_VALUE("db_queue_login", uint64(LoginDatabase.QueueSize()));
METRIC_VALUE("db_queue_character", uint64(CharacterDatabase.QueueSize()));
METRIC_VALUE("db_queue_world", uint64(WorldDatabase.QueueSize()));
sScriptMgr->OnMetricLogging();
});
METRIC_EVENT("events", "Worldserver started", "");
std::shared_ptr<void> sMetricHandle(nullptr, [](void*)
{
METRIC_EVENT("events", "Worldserver shutdown", "");
sMetric->Unload();
});
Acore::Module::SetEnableModulesList(AC_MODULES_LIST);
///- Initialize the World
sSecretMgr->Initialize();
sWorld->SetInitialWorldSettings();
std::shared_ptr<void> mapManagementHandle(nullptr, [](void*)
{
// unload battleground templates before different singletons destroyed
sBattlegroundMgr->DeleteAllBattlegrounds();
sOutdoorPvPMgr->Die(); // unload it before MapMgr
sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
sScriptMgr->OnAfterUnloadAllMaps();
});
// Start the Remote Access port (acceptor) if enabled
std::unique_ptr<AsyncAcceptor> raAcceptor;
if (sConfigMgr->GetOption<bool>("Ra.Enable", false))
{
raAcceptor.reset(StartRaSocketAcceptor(*ioContext));
}
// Start soap serving thread if enabled
std::shared_ptr<std::thread> soapThread;
if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false))
{
soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetOption<int32>("SOAP.Port", 7878))),
[](std::thread* thr)
{
thr->join();
delete thr;
});
}
// Launch the worldserver listener socket
uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
std::string worldListener = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
int networkThreads = sConfigMgr->GetOption<int32>("Network.Threads", 1);
if (networkThreads <= 0)
{
LOG_ERROR("server.worldserver", "Network.Threads must be greater than 0");
World::StopNow(ERROR_EXIT_CODE);
return 1;
}
if (!sWorldSocketMgr.StartWorldNetwork(*ioContext, worldListener, worldPort, networkThreads))
{
LOG_ERROR("server.worldserver", "Failed to initialize network");
World::StopNow(ERROR_EXIT_CODE);
return 1;
}
std::shared_ptr<void> sWorldSocketMgrHandle(nullptr, [](void*)
{
sWorld->KickAll(); // save and kick all players
sWorld->UpdateSessions(1); // real players unload required UpdateSessions call
sWorldSocketMgr.StopNetwork();
///- Clean database before leaving
ClearOnlineAccounts();
});
// Set server online (allow connecting now)
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag & ~{}, population = 0 WHERE id = '{}'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
realm.PopulationLevel = 0.0f;
realm.Flags = RealmFlags(realm.Flags & ~uint32(REALM_FLAG_VERSION_MISMATCH));
// Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec)
std::shared_ptr<FreezeDetector> freezeDetector;
if (int32 coreStuckTime = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 60))
{
freezeDetector = std::make_shared<FreezeDetector>(*ioContext, coreStuckTime * 1000);
FreezeDetector::Start(freezeDetector);
LOG_INFO("server.worldserver", "Starting up anti-freeze thread ({} seconds max stuck time)...", coreStuckTime);
}
LOG_INFO("server.worldserver", "{} (worldserver-daemon) ready...", GitRevision::GetFullVersion());
sScriptMgr->OnStartup();
// Launch CliRunnable thread
std::shared_ptr<std::thread> cliThread;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
#else
if (sConfigMgr->GetOption<bool>("Console.Enable", true))
#endif
{
cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread);
}
// Launch CliRunnable thread
std::shared_ptr<std::thread> auctionLisingThread;
auctionLisingThread.reset(new std::thread(AuctionListingRunnable),
[](std::thread* thr)
{
thr->join();
delete thr;
});
WorldUpdateLoop();
// Shutdown starts here
threadPool.reset();
sLog->SetSynchronous();
sScriptMgr->OnShutdown();
// set server offline
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, realm.Id.Realm);
LOG_INFO("server.worldserver", "Halting process...");
// 0 - normal shutdown
// 1 - shutdown at error
// 2 - restart command used, this code can be used by restarter for restart Warheadd
return World::GetExitCode();
}
/// Initialize connection to the databases
bool StartDB()
{
MySQL::Library_Init();
// Load databases
DatabaseLoader loader("server.worldserver", DatabaseLoader::DATABASE_NONE, AC_MODULES_LIST);
loader
.AddDatabase(LoginDatabase, "Login")
.AddDatabase(CharacterDatabase, "Character")
.AddDatabase(WorldDatabase, "World");
if (!loader.Load())
return false;
if (!sScriptMgr->OnDatabasesLoading())
{
return false;
}
///- Get the realm Id from the configuration file
realm.Id.Realm = sConfigMgr->GetOption<uint32>("RealmID", 0);
if (!realm.Id.Realm)
{
LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
return false;
}
else if (realm.Id.Realm > 255)
{
/*
* Due to the client only being able to read a realm.Id.Realm
* with a size of uint8 we can "only" store up to 255 realms
* anything further the client will behave anormaly
*/
LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255");
return false;
}
LOG_INFO("server.loading", "Loading world information...");
LOG_INFO("server.loading", "> RealmID: {}", realm.Id.Realm);
///- Clean the database before starting
ClearOnlineAccounts();
///- Insert version info into DB
WorldDatabase.Execute("UPDATE version SET core_version = '{}', core_revision = '{}'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // One-time query
sWorld->LoadDBVersion();
LOG_INFO("server.loading", "> Version DB world: {}", sWorld->GetDBVersion());
sScriptMgr->OnAfterDatabasesLoaded(loader.GetUpdateFlags());
return true;
}
void StopDB()
{
CharacterDatabase.Close();
WorldDatabase.Close();
LoginDatabase.Close();
sScriptMgr->OnDatabasesClosing();
MySQL::Library_End();
}
/// Clear 'online' status for all accounts with characters in this realm
void ClearOnlineAccounts()
{
// Reset online status for all accounts with characters on the current realm
// pussywizard: tc query would set online=0 even if logged in on another realm >_>
LoginDatabase.DirectExecute("UPDATE account SET online = 0 WHERE online = {}", realm.Id.Realm);
// Reset online status for all characters
CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
}
void ShutdownCLIThread(std::thread* cliThread)
{
if (cliThread)
{
#ifdef _WIN32
// First try to cancel any I/O in the CLI thread
if (!CancelSynchronousIo(cliThread->native_handle()))
{
// if CancelSynchronousIo() fails, print the error and try with old way
DWORD errorCode = GetLastError();
LPCSTR errorBuffer;
DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr);
if (!formatReturnCode)
errorBuffer = "Unknown error";
LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code {}, detail: {}", uint32(errorCode), errorBuffer);
if (!formatReturnCode)
LocalFree((LPSTR)errorBuffer);
// send keyboard input to safely unblock the CLI thread
INPUT_RECORD b[4];
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
b[0].EventType = KEY_EVENT;
b[0].Event.KeyEvent.bKeyDown = TRUE;
b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
b[0].Event.KeyEvent.wRepeatCount = 1;
b[1].EventType = KEY_EVENT;
b[1].Event.KeyEvent.bKeyDown = FALSE;
b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
b[1].Event.KeyEvent.wRepeatCount = 1;
b[2].EventType = KEY_EVENT;
b[2].Event.KeyEvent.bKeyDown = TRUE;
b[2].Event.KeyEvent.dwControlKeyState = 0;
b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[2].Event.KeyEvent.wRepeatCount = 1;
b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].EventType = KEY_EVENT;
b[3].Event.KeyEvent.bKeyDown = FALSE;
b[3].Event.KeyEvent.dwControlKeyState = 0;
b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].Event.KeyEvent.wRepeatCount = 1;
DWORD numb;
WriteConsoleInput(hStdIn, b, 4, &numb);
}
#endif
cliThread->join();
delete cliThread;
}
}
void WorldUpdateLoop()
{
uint32 realCurrTime = 0;
uint32 realPrevTime = getMSTime();
LoginDatabase.WarnAboutSyncQueries(true);
CharacterDatabase.WarnAboutSyncQueries(true);
WorldDatabase.WarnAboutSyncQueries(true);
sScriptMgr->OnDatabaseWarnAboutSyncQueries(true);
///- While we have not World::m_stopEvent, update the world
while (!World::IsStopped())
{
++World::m_worldLoopCounter;
realCurrTime = getMSTime();
uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime);
sWorld->Update(diff);
realPrevTime = realCurrTime;
uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime());
// we know exactly how long it took to update the world, if the update took less than WORLD_SLEEP_CONST, sleep for WORLD_SLEEP_CONST - world update time
if (executionTimeDiff < WORLD_SLEEP_CONST)
{
std::this_thread::sleep_for(Milliseconds(WORLD_SLEEP_CONST - executionTimeDiff));
}
#ifdef _WIN32
if (m_ServiceStatus == 0)
World::StopNow(SHUTDOWN_EXIT_CODE);
while (m_ServiceStatus == 2)
Sleep(1000);
#endif
}
sScriptMgr->OnDatabaseWarnAboutSyncQueries(false);
LoginDatabase.WarnAboutSyncQueries(false);
CharacterDatabase.WarnAboutSyncQueries(false);
WorldDatabase.WarnAboutSyncQueries(false);
}
void SignalHandler(boost::system::error_code const& error, int /*signalNumber*/)
{
if (!error)
World::StopNow(SHUTDOWN_EXIT_CODE);
}
void FreezeDetector::Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<FreezeDetector> freezeDetector = freezeDetectorRef.lock())
{
uint32 curtime = getMSTime();
uint32 worldLoopCounter = World::m_worldLoopCounter;
if (freezeDetector->_worldLoopCounter != worldLoopCounter)
{
freezeDetector->_lastChangeMsTime = curtime;
freezeDetector->_worldLoopCounter = worldLoopCounter;
}
// possible freeze
else if (getMSTimeDiff(freezeDetector->_lastChangeMsTime, curtime) > freezeDetector->_maxCoreStuckTimeInMs)
{
LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!");
ABORT();
}
freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(1));
freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, freezeDetectorRef, std::placeholders::_1));
}
}
}
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext)
{
uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443));
std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0");
AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort);
if (!acceptor->Bind())
{
LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor");
delete acceptor;
return nullptr;
}
acceptor->AsyncAccept<RASession>();
return acceptor;
}
bool LoadRealmInfo(Acore::Asio::IoContext& ioContext)
{
QueryResult result = LoginDatabase.Query("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = {}", realm.Id.Realm);
if (!result)
return false;
Acore::Asio::Resolver resolver(ioContext);
Field* fields = result->Fetch();
realm.Name = fields[1].Get<std::string>();
Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[2].Get<std::string>(), "");
if (!externalAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[2].Get<std::string>());
return false;
}
realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(externalAddress->address());
Optional<boost::asio::ip::tcp::endpoint> localAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[3].Get<std::string>(), "");
if (!localAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[3].Get<std::string>());
return false;
}
realm.LocalAddress = std::make_unique<boost::asio::ip::address>(localAddress->address());
Optional<boost::asio::ip::tcp::endpoint> localSubmask = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[4].Get<std::string>(), "");
if (!localSubmask)
{
LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[4].Get<std::string>());
return false;
}
realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(localSubmask->address());
realm.Port = fields[5].Get<uint16>();
realm.Type = fields[6].Get<uint8>();
realm.Flags = RealmFlags(fields[7].Get<uint8>());
realm.Timezone = fields[8].Get<uint8>();
realm.AllowedSecurityLevel = AccountTypes(fields[9].Get<uint8>());
realm.PopulationLevel = fields[10].Get<float>();
realm.Build = fields[11].Get<uint32>();
return true;
}
void AuctionListingRunnable()
{
LOG_INFO("server", "Starting up Auction House Listing thread...");
while (!World::IsStopped())
{
if (AsyncAuctionListingMgr::IsAuctionListingAllowed())
{
uint32 diff = AsyncAuctionListingMgr::GetDiff();
AsyncAuctionListingMgr::ResetDiff();
if (AsyncAuctionListingMgr::GetTempList().size() || AsyncAuctionListingMgr::GetList().size())
{
std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetLock());
{
std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetTempLock());
for (auto const& delayEvent : AsyncAuctionListingMgr::GetTempList())
AsyncAuctionListingMgr::GetList().emplace_back(delayEvent);
AsyncAuctionListingMgr::GetTempList().clear();
}
for (auto& itr : AsyncAuctionListingMgr::GetList())
{
if (itr._msTimer <= diff)
itr._msTimer = 0;
else
itr._msTimer -= diff;
}
for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr)
{
if ((*itr)._msTimer != 0)
continue;
if ((*itr).Execute())
AsyncAuctionListingMgr::GetList().erase(itr);
break;
}
}
}
std::this_thread::sleep_for(1ms);
}
LOG_INFO("server", "Auction House Listing thread exiting without problems.");
}
void ShutdownAuctionListingThread(std::thread* thread)
{
if (thread)
{
thread->join();
delete thread;
}
}
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& configService)
{
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_CORE_CONFIG))), "use <arg> as configuration file");
#if AC_PLATFORM == WARHEAD_PLATFORM_WINDOWS
options_description win("Windows platform specific options");
win.add_options()
("service,s", value<std::string>(&configService)->default_value(""), "Windows service options: [install | uninstall]");
all.add(win);
#endif
variables_map vm;
try
{
store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), vm);
notify(vm);
}
catch (std::exception const& e)
{
std::cerr << e.what() << "\n";
}
if (vm.count("help"))
{
std::cout << all << "\n";
}
else if (vm.count("dry-run"))
{
sConfigMgr->setDryRun(true);
}
return vm;
}

View File

@@ -0,0 +1,24 @@
/*
* 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 "Util.h"
#include "World.h"
#include "WorldSocket.h"

View File

@@ -0,0 +1,223 @@
/*
* 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 "RASession.h"
#include "AccountMgr.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "Duration.h"
#include "Log.h"
#include "SRP6.h"
#include "ServerMotd.h"
#include "Util.h"
#include "World.h"
#include <boost/asio/buffer.hpp>
#include <boost/asio/read_until.hpp>
#include <thread>
using boost::asio::ip::tcp;
void RASession::Start()
{
// wait 1 second for active connections to send negotiation request
for (int counter = 0; counter < 10 && _socket.available() == 0; counter++)
std::this_thread::sleep_for(100ms);
// Check if there are bytes available, if they are, then the client is requesting the negotiation
if (_socket.available() > 0)
{
// Handle subnegotiation
char buf[1024] = { };
_socket.read_some(boost::asio::buffer(buf));
// Send the end-of-negotiation packet
uint8 const reply[2] = { 0xFF, 0xF0 };
_socket.write_some(boost::asio::buffer(reply));
}
Send("Authentication Required\r\n");
Send("Username: ");
std::string username = ReadString();
if (username.empty())
return;
LOG_INFO("commands.ra", "Accepting RA connection from user {} (IP: {})", username, GetRemoteIpAddress());
Send("Password: ");
std::string password = ReadString();
if (password.empty())
return;
if (!CheckAccessLevel(username) || !CheckPassword(username, password))
{
Send("Authentication failed\r\n");
_socket.close();
return;
}
LOG_INFO("commands.ra", "User {} (IP: {}) authenticated correctly to RA", username, GetRemoteIpAddress());
// Authentication successful, send the motd
Send(std::string(std::string(Motd::GetMotd()) + "\r\n").c_str());
// Read commands
for (;;)
{
Send("AC>");
std::string command = ReadString();
if (ProcessCommand(command))
break;
}
_socket.close();
}
int RASession::Send(std::string_view data)
{
std::ostream os(&_writeBuffer);
os << data;
size_t written = _socket.send(_writeBuffer.data());
_writeBuffer.consume(written);
return written;
}
std::string RASession::ReadString()
{
boost::system::error_code error;
size_t read = boost::asio::read_until(_socket, _readBuffer, "\r\n", error);
if (!read)
{
_socket.close();
return "";
}
std::string line;
std::istream is(&_readBuffer);
std::getline(is, line);
if (*line.rbegin() == '\r')
line.erase(line.length() - 1);
return line;
}
bool RASession::CheckAccessLevel(const std::string& user)
{
std::string safeUser = user;
Utf8ToUpperOnlyLatin(safeUser);
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ACCESS);
stmt->SetData(0, safeUser);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (!result)
{
LOG_INFO("commands.ra", "User {} does not exist in database", user);
return false;
}
Field* fields = result->Fetch();
if (fields[1].Get<uint8>() < sConfigMgr->GetOption<int32>("Ra.MinLevel", 3))
{
LOG_INFO("commands.ra", "User {} has no privilege to login", user);
return false;
}
else if (fields[2].Get<int32>() != -1)
{
LOG_INFO("commands.ra", "User {} has to be assigned on all realms (with RealmID = '-1')", user);
return false;
}
return true;
}
bool RASession::CheckPassword(const std::string& user, const std::string& pass)
{
std::string safe_user = user;
std::transform(safe_user.begin(), safe_user.end(), safe_user.begin(), ::toupper);
Utf8ToUpperOnlyLatin(safe_user);
std::string safe_pass = pass;
Utf8ToUpperOnlyLatin(safe_pass);
std::transform(safe_pass.begin(), safe_pass.end(), safe_pass.begin(), ::toupper);
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME);
stmt->SetData(0, safe_user);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
{
Acore::Crypto::SRP6::Salt salt = (*result)[0].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>();
Acore::Crypto::SRP6::Verifier verifier = (*result)[1].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>();
if (Acore::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier))
return true;
}
LOG_INFO("commands.ra", "Wrong password for user: {}", user);
return false;
}
bool RASession::ProcessCommand(std::string& command)
{
if (command.length() == 0)
return true;
LOG_INFO("commands.ra", "Received command: {}", command);
// handle quit, exit and logout commands to terminate connection
if (command == "quit" || command == "exit" || command == "logout")
{
Send("Bye\r\n");
return true;
}
// Obtain a new promise per command
delete _commandExecuting;
_commandExecuting = new std::promise<void>();
CliCommandHolder* cmd = new CliCommandHolder(this, command.c_str(), &RASession::CommandPrint, &RASession::CommandFinished);
sWorld->QueueCliCommand(cmd);
// Wait for the command to finish
_commandExecuting->get_future().wait();
return false;
}
void RASession::CommandPrint(void* callbackArg, std::string_view text)
{
if (text.empty())
{
return;
}
RASession* session = static_cast<RASession*>(callbackArg);
session->Send(text);
}
void RASession::CommandFinished(void* callbackArg, bool /*success*/)
{
RASession* session = static_cast<RASession*>(callbackArg);
session->_commandExecuting->set_value();
}

View File

@@ -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 __RASESSION_H__
#define __RASESSION_H__
#include "Common.h"
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/streambuf.hpp>
#include <future>
#include <memory>
using boost::asio::ip::tcp;
const size_t bufferSize = 4096;
class RASession : public std::enable_shared_from_this<RASession>
{
public:
RASession(tcp::socket&& socket) :
_socket(std::move(socket)), _commandExecuting(nullptr) { }
void Start();
const std::string GetRemoteIpAddress() const { return _socket.remote_endpoint().address().to_string(); }
unsigned short GetRemotePort() const { return _socket.remote_endpoint().port(); }
private:
int Send(std::string_view data);
std::string ReadString();
bool CheckAccessLevel(const std::string& user);
bool CheckPassword(const std::string& user, const std::string& pass);
bool ProcessCommand(std::string& command);
static void CommandPrint(void* callbackArg, std::string_view text);
static void CommandFinished(void* callbackArg, bool);
tcp::socket _socket;
boost::asio::streambuf _readBuffer;
boost::asio::streambuf _writeBuffer;
std::promise<void>* _commandExecuting;
};
#endif

View File

@@ -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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -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"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APPICON ICON "worldserver.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", "World Server Daemon"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "worldserver"
VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR
VALUE "OriginalFilename", "worldserver.exe"
VALUE "ProductName", "World Server"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x800, 1200
END
END
#endif