mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-13 09:17:18 +00:00
feat(Core/RASession): switch to boost api (#6172)
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include "ACSoap.h"
|
||||
#include "AsyncAcceptor.h"
|
||||
#include "BigNumber.h"
|
||||
#include "CliRunnable.h"
|
||||
#include "Common.h"
|
||||
@@ -16,10 +17,11 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DatabaseWorkerPool.h"
|
||||
#include "GitRevision.h"
|
||||
#include "IoContext.h"
|
||||
#include "Log.h"
|
||||
#include "Master.h"
|
||||
#include "OpenSSLCrypto.h"
|
||||
#include "RARunnable.h"
|
||||
#include "RASession.h"
|
||||
#include "Realm.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SignalHandler.h"
|
||||
@@ -97,6 +99,7 @@ public:
|
||||
};
|
||||
|
||||
bool LoadRealmInfo();
|
||||
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext);
|
||||
|
||||
Master* Master::instance()
|
||||
{
|
||||
@@ -107,6 +110,8 @@ Master* Master::instance()
|
||||
/// Main function
|
||||
int Master::Run()
|
||||
{
|
||||
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
|
||||
|
||||
OpenSSLCrypto::threadsSetup();
|
||||
BigNumber seed1;
|
||||
seed1.SetRand(16 * 8);
|
||||
@@ -171,12 +176,15 @@ int Master::Run()
|
||||
cliThread = new Acore::Thread(new CliRunnable);
|
||||
}
|
||||
|
||||
Acore::Thread rarThread(new RARunnable);
|
||||
|
||||
// pussywizard:
|
||||
Acore::Thread auctionLising_thread(new AuctionListingRunnable);
|
||||
auctionLising_thread.setPriority(Acore::Priority_High);
|
||||
|
||||
// Start the Remote Access port (acceptor) if enabled
|
||||
std::unique_ptr<AsyncAcceptor> raAcceptor;
|
||||
if (sConfigMgr->GetOption<bool>("Ra.Enable", false))
|
||||
raAcceptor.reset(StartRaSocketAcceptor(*ioContext));
|
||||
|
||||
// Start soap serving thread if enabled
|
||||
std::shared_ptr<std::thread> soapThread;
|
||||
if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false))
|
||||
@@ -216,7 +224,6 @@ int Master::Run()
|
||||
// when the main thread closes the singletons get unloaded
|
||||
// since worldrunnable uses them, it will crash if unloaded after master
|
||||
worldThread.wait();
|
||||
rarThread.wait();
|
||||
auctionLising_thread.wait();
|
||||
|
||||
if (freezeThread)
|
||||
@@ -408,3 +415,20 @@ bool LoadRealmInfo()
|
||||
realm.Build = fields[11].GetUInt32();
|
||||
return true;
|
||||
}
|
||||
|
||||
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext)
|
||||
{
|
||||
uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443));
|
||||
std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0");
|
||||
|
||||
AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort);
|
||||
if (!acceptor->Bind())
|
||||
{
|
||||
LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor");
|
||||
delete acceptor;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
acceptor->AsyncAccept<RASession>();
|
||||
return acceptor;
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*/
|
||||
|
||||
/** \file
|
||||
\ingroup Trinityd
|
||||
*/
|
||||
|
||||
#include "Common.h"
|
||||
#include "Config.h"
|
||||
#include "Log.h"
|
||||
#include "RARunnable.h"
|
||||
#include "RASocket.h"
|
||||
#include "World.h"
|
||||
#include <ace/Acceptor.h>
|
||||
#include <ace/Dev_Poll_Reactor.h>
|
||||
#include <ace/Reactor_Impl.h>
|
||||
#include <ace/SOCK_Acceptor.h>
|
||||
#include <ace/TP_Reactor.h>
|
||||
|
||||
RARunnable::RARunnable()
|
||||
{
|
||||
ACE_Reactor_Impl* imp;
|
||||
|
||||
#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL)
|
||||
imp = new ACE_Dev_Poll_Reactor();
|
||||
imp->max_notify_iterations (128);
|
||||
imp->restart (1);
|
||||
#else
|
||||
imp = new ACE_TP_Reactor();
|
||||
imp->max_notify_iterations (128);
|
||||
#endif
|
||||
|
||||
m_Reactor = new ACE_Reactor (imp, 1);
|
||||
}
|
||||
|
||||
RARunnable::~RARunnable()
|
||||
{
|
||||
delete m_Reactor;
|
||||
}
|
||||
|
||||
void RARunnable::run()
|
||||
{
|
||||
if (!sConfigMgr->GetOption<bool>("Ra.Enable", false))
|
||||
return;
|
||||
|
||||
ACE_Acceptor<RASocket, ACE_SOCK_ACCEPTOR> acceptor;
|
||||
|
||||
uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443));
|
||||
std::string stringIp = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0");
|
||||
ACE_INET_Addr listenAddress(raPort, stringIp.c_str());
|
||||
|
||||
if (acceptor.open(listenAddress, m_Reactor) == -1)
|
||||
{
|
||||
LOG_ERROR("server", "Trinity RA can not bind to port %d on %s (possible error: port already in use)", raPort, stringIp.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("server", "Starting Trinity RA on port %d on %s", raPort, stringIp.c_str());
|
||||
|
||||
while (!World::IsStopped())
|
||||
{
|
||||
ACE_Time_Value interval(0, 100000);
|
||||
if (m_Reactor->run_reactor_event_loop(interval) == -1)
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS)
|
||||
LOG_DEBUG("server", "Trinity RA thread exiting");
|
||||
#endif
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*/
|
||||
|
||||
/// \addtogroup Trinityd
|
||||
/// @{
|
||||
/// \file
|
||||
|
||||
#ifndef _ACORE_RARUNNABLE_H_
|
||||
#define _ACORE_RARUNNABLE_H_
|
||||
|
||||
#include "Common.h"
|
||||
#include "Threading.h"
|
||||
#include <ace/Reactor.h>
|
||||
|
||||
class RARunnable : public Acore::Runnable
|
||||
{
|
||||
public:
|
||||
RARunnable();
|
||||
~RARunnable() override;
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
ACE_Reactor* m_Reactor;
|
||||
};
|
||||
|
||||
#endif /* _ACORE_RARUNNABLE_H_ */
|
||||
|
||||
/// @}
|
||||
212
src/server/worldserver/RemoteAccess/RASession.cpp
Normal file
212
src/server/worldserver/RemoteAccess/RASession.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
|
||||
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
|
||||
*/
|
||||
|
||||
#include "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 <memory>
|
||||
#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 %s (IP: %s)", username.c_str(), GetRemoteIpAddress().c_str());
|
||||
|
||||
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 %s (IP: %s) authenticated correctly to RA", username.c_str(), GetRemoteIpAddress().c_str());
|
||||
|
||||
// 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(const char* 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->setString(0, safeUser);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_INFO("commands.ra", "User %s does not exist in database", user.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
if (fields[1].GetUInt8() < sConfigMgr->GetOption<int32>("Ra.MinLevel", 3))
|
||||
{
|
||||
LOG_INFO("commands.ra", "User %s has no privilege to login", user.c_str());
|
||||
return false;
|
||||
}
|
||||
else if (fields[2].GetInt32() != -1)
|
||||
{
|
||||
LOG_INFO("commands.ra", "User %s has to be assigned on all realms (with RealmID = '-1')", user.c_str());
|
||||
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->setString(0, safe_user);
|
||||
|
||||
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
|
||||
{
|
||||
Acore::Crypto::SRP6::Salt salt = (*result)[0].GetBinary<Acore::Crypto::SRP6::SALT_LENGTH>();
|
||||
Acore::Crypto::SRP6::Verifier verifier = (*result)[1].GetBinary<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: %s", user.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RASession::ProcessCommand(std::string& command)
|
||||
{
|
||||
if (command.length() == 0)
|
||||
return true;
|
||||
|
||||
LOG_INFO("commands.ra", "Received command: %s", command.c_str());
|
||||
|
||||
// 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, const char* text)
|
||||
{
|
||||
if (!text || !*text)
|
||||
{
|
||||
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();
|
||||
}
|
||||
46
src/server/worldserver/RemoteAccess/RASession.h
Normal file
46
src/server/worldserver/RemoteAccess/RASession.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
|
||||
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
|
||||
*/
|
||||
|
||||
#ifndef __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(const char* 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, const char* text);
|
||||
static void CommandFinished(void* callbackArg, bool);
|
||||
|
||||
tcp::socket _socket;
|
||||
boost::asio::streambuf _readBuffer;
|
||||
boost::asio::streambuf _writeBuffer;
|
||||
std::promise<void>* _commandExecuting;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*/
|
||||
|
||||
/** \file
|
||||
\ingroup Trinityd
|
||||
*/
|
||||
|
||||
#include "AccountMgr.h"
|
||||
#include "Common.h"
|
||||
#include "Config.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Duration.h"
|
||||
#include "Log.h"
|
||||
#include "RASocket.h"
|
||||
#include "ServerMotd.h"
|
||||
#include "SRP6.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include <thread>
|
||||
|
||||
RASocket::RASocket()
|
||||
{
|
||||
_minLevel = uint8(sConfigMgr->GetOption<int32>("RA.MinLevel", 3));
|
||||
_commandExecuting = false;
|
||||
}
|
||||
|
||||
int RASocket::open(void*)
|
||||
{
|
||||
ACE_INET_Addr remoteAddress;
|
||||
|
||||
if (peer().get_remote_addr(remoteAddress) == -1)
|
||||
{
|
||||
LOG_ERROR("server", "RASocket::open: peer().get_remote_addr error is %s", ACE_OS::strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INFO("network", "Incoming connection from %s", remoteAddress.get_host_addr());
|
||||
|
||||
return activate();
|
||||
}
|
||||
|
||||
int RASocket::handle_close(ACE_HANDLE /*handle*/, ACE_Reactor_Mask /*mask*/)
|
||||
{
|
||||
LOG_INFO("network", "Closing connection");
|
||||
peer().close_reader();
|
||||
wait();
|
||||
// While the above wait() will wait for the ::svc() to finish, it will not wait for the async event
|
||||
// RASocket::commandfinished to be completed. Calling destroy() before the latter function ends
|
||||
// will lead to using a freed pointer -> crash.
|
||||
while (_commandExecuting)
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RASocket::send(const std::string& line)
|
||||
{
|
||||
#ifdef MSG_NOSIGNAL
|
||||
ssize_t n = peer().send(line.c_str(), line.length(), MSG_NOSIGNAL);
|
||||
#else
|
||||
ssize_t n = peer().send(line.c_str(), line.length());
|
||||
#endif // MSG_NOSIGNAL
|
||||
|
||||
return n == ssize_t(line.length()) ? 0 : -1;
|
||||
}
|
||||
|
||||
int RASocket::recv_line(ACE_Message_Block& buffer)
|
||||
{
|
||||
char byte;
|
||||
for (;;)
|
||||
{
|
||||
ssize_t n = peer().recv(&byte, sizeof(byte));
|
||||
|
||||
if (n < 0)
|
||||
return -1;
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
// EOF, connection was closed
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ASSERT(n == sizeof(byte));
|
||||
|
||||
if (byte == '\n')
|
||||
break;
|
||||
else if (byte == '\r') /* Ignore CR */
|
||||
continue;
|
||||
else if (buffer.copy(&byte, sizeof(byte)) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char nullTerm = '\0';
|
||||
if (buffer.copy(&nullTerm, sizeof(nullTerm)) == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RASocket::recv_line(std::string& out_line)
|
||||
{
|
||||
char buf[4096];
|
||||
|
||||
ACE_Data_Block db(sizeof (buf),
|
||||
ACE_Message_Block::MB_DATA,
|
||||
buf,
|
||||
0,
|
||||
0,
|
||||
ACE_Message_Block::DONT_DELETE,
|
||||
0);
|
||||
|
||||
ACE_Message_Block message_block(&db,
|
||||
ACE_Message_Block::DONT_DELETE,
|
||||
0);
|
||||
|
||||
if (recv_line(message_block) == -1)
|
||||
{
|
||||
LOG_INFO("network", "Recv error %s", ACE_OS::strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
out_line = message_block.rd_ptr();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RASocket::process_command(const std::string& command)
|
||||
{
|
||||
if (command.length() == 0)
|
||||
return 0;
|
||||
|
||||
LOG_INFO("network", "Got command: %s", command.c_str());
|
||||
|
||||
// handle quit, exit and logout commands to terminate connection
|
||||
if (command == "quit" || command == "exit" || command == "logout")
|
||||
{
|
||||
(void) send("Bye\r\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
_commandExecuting = true;
|
||||
CliCommandHolder* cmd = new CliCommandHolder(this, command.c_str(), &RASocket::zprint, &RASocket::commandFinished);
|
||||
sWorld->QueueCliCommand(cmd);
|
||||
|
||||
// wait for result
|
||||
ACE_Message_Block* mb;
|
||||
for (;;)
|
||||
{
|
||||
if (getq(mb) == -1)
|
||||
return -1;
|
||||
|
||||
if (mb->msg_type() == ACE_Message_Block::MB_BREAK)
|
||||
{
|
||||
mb->release();
|
||||
break;
|
||||
}
|
||||
|
||||
if (send(std::string(mb->rd_ptr(), mb->length())) == -1)
|
||||
{
|
||||
mb->release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
mb->release();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RASocket::check_access_level(const std::string& user)
|
||||
{
|
||||
std::string safeUser = user;
|
||||
|
||||
Utf8ToUpperOnlyLatin(safeUser);
|
||||
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ACCESS);
|
||||
stmt->setString(0, safeUser);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_INFO("network", "User %s does not exist in database", user.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
if (fields[1].GetUInt8() < _minLevel)
|
||||
{
|
||||
LOG_INFO("network", "User %s has no privilege to login", user.c_str());
|
||||
return -1;
|
||||
}
|
||||
else if (fields[2].GetInt32() != -1)
|
||||
{
|
||||
LOG_INFO("network", "User %s has to be assigned on all realms (with RealmID = '-1')", user.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RASocket::check_password(const std::string& user, const std::string& pass)
|
||||
{
|
||||
std::string safe_user = user;
|
||||
Utf8ToUpperOnlyLatin(safe_user);
|
||||
|
||||
std::string safe_pass = pass;
|
||||
Utf8ToUpperOnlyLatin(safe_pass);
|
||||
|
||||
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME);
|
||||
|
||||
stmt->setString(0, safe_user);
|
||||
|
||||
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
|
||||
{
|
||||
Acore::Crypto::SRP6::Salt salt = (*result)[0].GetBinary<Acore::Crypto::SRP6::SALT_LENGTH>();
|
||||
Acore::Crypto::SRP6::Verifier verifier = (*result)[1].GetBinary<Acore::Crypto::SRP6::VERIFIER_LENGTH>();
|
||||
|
||||
if (Acore::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier))
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_INFO("commands.ra", "Wrong password for user: %s", user.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int RASocket::authenticate()
|
||||
{
|
||||
if (send(std::string("Username: ")) == -1)
|
||||
return -1;
|
||||
|
||||
std::string user;
|
||||
if (recv_line(user) == -1)
|
||||
return -1;
|
||||
|
||||
if (send(std::string("Password: ")) == -1)
|
||||
return -1;
|
||||
|
||||
std::string pass;
|
||||
if (recv_line(pass) == -1)
|
||||
return -1;
|
||||
|
||||
LOG_INFO("network", "Login attempt for user: %s", user.c_str());
|
||||
|
||||
if (check_access_level(user) == -1)
|
||||
return -1;
|
||||
|
||||
if (check_password(user, pass) == -1)
|
||||
return -1;
|
||||
|
||||
LOG_INFO("network", "User login: %s", user.c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RASocket::subnegotiate()
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
ACE_Data_Block db(sizeof (buf),
|
||||
ACE_Message_Block::MB_DATA,
|
||||
buf,
|
||||
0,
|
||||
0,
|
||||
ACE_Message_Block::DONT_DELETE,
|
||||
0);
|
||||
|
||||
ACE_Message_Block message_block(&db,
|
||||
ACE_Message_Block::DONT_DELETE,
|
||||
0);
|
||||
|
||||
const size_t recv_size = message_block.space();
|
||||
|
||||
// Wait a maximum of 1000ms for negotiation packet - not all telnet clients may send it
|
||||
ACE_Time_Value waitTime = ACE_Time_Value(1);
|
||||
const ssize_t n = peer().recv(message_block.wr_ptr(),
|
||||
recv_size, &waitTime);
|
||||
|
||||
if (n <= 0)
|
||||
return int(n);
|
||||
|
||||
if (n >= 1024)
|
||||
{
|
||||
LOG_INFO("network", "RASocket::subnegotiate: allocated buffer 1024 bytes was too small for negotiation packet, size: %u", uint32(n));
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf[n] = '\0';
|
||||
|
||||
#ifdef _DEBUG
|
||||
for (uint8 i = 0; i < n; )
|
||||
{
|
||||
uint8 iac = buf[i];
|
||||
if (iac == 0xFF) // "Interpret as Command" (IAC)
|
||||
{
|
||||
uint8 command = buf[++i];
|
||||
std::stringstream ss;
|
||||
switch (command)
|
||||
{
|
||||
case 0xFB: // WILL
|
||||
ss << "WILL ";
|
||||
break;
|
||||
case 0xFC: // WON'T
|
||||
ss << "WON'T ";
|
||||
break;
|
||||
case 0xFD: // DO
|
||||
ss << "DO ";
|
||||
break;
|
||||
case 0xFE: // DON'T
|
||||
ss << "DON'T ";
|
||||
break;
|
||||
default:
|
||||
return -1; // not allowed
|
||||
}
|
||||
|
||||
uint8 param = buf[++i];
|
||||
ss << uint32(param);
|
||||
LOG_INFO("network", ss.str().c_str());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
#endif
|
||||
|
||||
//! Just send back end of subnegotiation packet
|
||||
uint8 const reply[2] = {0xFF, 0xF0};
|
||||
|
||||
#ifdef MSG_NOSIGNAL
|
||||
return int(peer().send(reply, 2, MSG_NOSIGNAL));
|
||||
#else
|
||||
return int(peer().send(reply, 2));
|
||||
#endif // MSG_NOSIGNAL
|
||||
}
|
||||
|
||||
int RASocket::svc(void)
|
||||
{
|
||||
//! Subnegotiation may differ per client - do not react on it
|
||||
subnegotiate();
|
||||
|
||||
if (send("Authentication required\r\n") == -1)
|
||||
return -1;
|
||||
|
||||
if (authenticate() == -1)
|
||||
{
|
||||
(void) send("Authentication failed\r\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// send motd
|
||||
if (send(std::string(Motd::GetMotd()) + "\r\n") == -1)
|
||||
return -1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// show prompt
|
||||
if (send("AC> ") == -1)
|
||||
return -1;
|
||||
|
||||
std::string line;
|
||||
|
||||
if (recv_line(line) == -1)
|
||||
return -1;
|
||||
|
||||
if (process_command(line) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RASocket::zprint(void* callbackArg, const char* szText)
|
||||
{
|
||||
if (!szText || !callbackArg)
|
||||
return;
|
||||
|
||||
RASocket* socket = static_cast<RASocket*>(callbackArg);
|
||||
size_t sz = strlen(szText);
|
||||
|
||||
ACE_Message_Block* mb = new ACE_Message_Block(sz);
|
||||
mb->copy(szText, sz);
|
||||
|
||||
ACE_Time_Value tv = ACE_Time_Value::zero;
|
||||
if (socket->putq(mb, &tv) == -1)
|
||||
{
|
||||
LOG_INFO("network", "Failed to enqueue message, queue is full or closed. Error is %s", ACE_OS::strerror(errno));
|
||||
mb->release();
|
||||
}
|
||||
}
|
||||
|
||||
void RASocket::commandFinished(void* callbackArg, bool /*success*/)
|
||||
{
|
||||
if (!callbackArg)
|
||||
return;
|
||||
|
||||
RASocket* socket = static_cast<RASocket*>(callbackArg);
|
||||
|
||||
ACE_Message_Block* mb = new ACE_Message_Block();
|
||||
|
||||
mb->msg_type(ACE_Message_Block::MB_BREAK);
|
||||
|
||||
// the message is 0 size control message to tell that command output is finished
|
||||
// hence we don't put timeout, because it shouldn't increase queue size and shouldn't block
|
||||
if (socket->putq(mb->duplicate()) == -1)
|
||||
{
|
||||
// getting here is bad, command can't be marked as complete
|
||||
//LOG_DEBUG("misc", "Failed to enqueue command end message. Error is %s", ACE_OS::strerror(errno));
|
||||
}
|
||||
|
||||
mb->release();
|
||||
|
||||
socket->_commandExecuting = false;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*/
|
||||
|
||||
/// \addtogroup Trinityd
|
||||
/// @{
|
||||
/// \file
|
||||
|
||||
#ifndef _RASOCKET_H
|
||||
#define _RASOCKET_H
|
||||
|
||||
#include "Common.h"
|
||||
#include <ace/SOCK_Acceptor.h>
|
||||
#include <ace/SOCK_Stream.h>
|
||||
#include <ace/Svc_Handler.h>
|
||||
#include <ace/Synch_Traits.h>
|
||||
#include <atomic>
|
||||
|
||||
/// Remote Administration socket
|
||||
class RASocket : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_MT_SYNCH>
|
||||
{
|
||||
public:
|
||||
RASocket();
|
||||
virtual ~RASocket() { }
|
||||
|
||||
virtual int svc();
|
||||
virtual int open(void* = 0);
|
||||
virtual int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK);
|
||||
|
||||
private:
|
||||
int recv_line(std::string& outLine);
|
||||
int recv_line(ACE_Message_Block& buffer);
|
||||
int process_command(const std::string& command);
|
||||
int authenticate();
|
||||
int subnegotiate(); ///< Used by telnet protocol RFC 854 / 855
|
||||
int check_access_level(const std::string& user);
|
||||
int check_password(const std::string& user, const std::string& pass);
|
||||
int send(const std::string& line);
|
||||
|
||||
static void zprint(void* callbackArg, const char* szText);
|
||||
static void commandFinished(void* callbackArg, bool success);
|
||||
|
||||
private:
|
||||
uint8 _minLevel; ///< Minimum security level required to connect
|
||||
std::atomic_long _commandExecuting;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
Reference in New Issue
Block a user