RCON system overhaul

* Implemented robust length-prefix framing logic for non-blocking sockets (previously used character sequences to determine length, but you cannot use character sequences on protocol buffers as its binary data. This logic should fix all problems regarding some commands not getting networked properly to the server and stuff not getting printed on the client).
* Increased buffer size to std::vector::max_size when netconsole is authenticated (MAX_NETCONSOLE_INPUT_LEN still remains enforced on accepted but not authenticated connections to prevent attackers from crashing the server).
* Process max 1024 bytes each recv buffer iteration.
* Additional optimizations and cleanup.
This commit is contained in:
Kawe Mazidjatari 2022-08-02 23:58:43 +02:00
parent 5795d15c83
commit 9775fc4bba
10 changed files with 394 additions and 199 deletions

View File

@ -30,21 +30,27 @@ enum class ServerDataResponseType_t : int
class CConnectedNetConsoleData
{
public:
SocketHandle_t m_hSocket {};
int m_nCharsInCommandBuffer {};
char m_pszInputCommandBuffer[MAX_NETCONSOLE_INPUT_LEN] {};
bool m_bValidated {}; // Revalidates netconsole if false.
bool m_bAuthorized {}; // Set to true after netconsole successfully authed.
bool m_bInputOnly {}; // If set, don't send spew to this net console.
int m_nFailedAttempts {}; // Num failed authentication attempts.
int m_nIgnoredMessage {}; // Count how many times client ignored the no-auth message.
SocketHandle_t m_hSocket;
int m_nPayloadLen; // Num bytes for this message.
int m_nPayloadRead; // Num read bytes from input buffer.
int m_nFailedAttempts; // Num failed authentication attempts.
int m_nIgnoredMessage; // Count how many times client ignored the no-auth message.
bool m_bValidated; // Revalidates netconsole if false.
bool m_bAuthorized; // Set to true after successfull netconsole auth.
bool m_bInputOnly; // If set, don't send spew to this net console.
std::vector<uint8_t> m_RecvBuffer;
CConnectedNetConsoleData(SocketHandle_t hSocket = -1)
{
m_nCharsInCommandBuffer = 0;
m_bAuthorized = false;
m_hSocket = hSocket;
m_bInputOnly = false;
m_hSocket = hSocket;
m_nPayloadLen = 0;
m_nPayloadRead = 0;
m_nFailedAttempts = 0;
m_nIgnoredMessage = 0;
m_bValidated = false;
m_bAuthorized = false;
m_bInputOnly = false;
m_RecvBuffer.reserve(sizeof(int)); // Reserve enough for length-prefix.
}
};

View File

@ -11,9 +11,31 @@
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
#include "engine/client/cl_rcon.h"
#include "engine/net.h"
#include "squirrel/sqvm.h"
#include "common/igameserverdata.h"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRConClient::CRConClient()
: m_bInitialized(false)
, m_bConnEstablished(false)
{
m_pNetAdr2 = new CNetAdr2("localhost", "37015");
m_pSocket = new CSocketCreator();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRConClient::~CRConClient(void)
{
delete m_pNetAdr2;
delete m_pSocket;
}
//-----------------------------------------------------------------------------
// Purpose: NETCON systems init
//-----------------------------------------------------------------------------
@ -119,7 +141,7 @@ bool CRConClient::Connect(const std::string& svInAdr, const std::string& svInPor
//-----------------------------------------------------------------------------
void CRConClient::Disconnect(void)
{
::closesocket(m_pSocket->GetAcceptedSocketHandle(0));
m_pSocket->CloseAcceptedSocket(0);
m_bConnEstablished = false;
}
@ -129,7 +151,16 @@ void CRConClient::Disconnect(void)
//-----------------------------------------------------------------------------
void CRConClient::Send(const std::string& svMessage) const
{
int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, svMessage.c_str(), svMessage.size(), MSG_NOSIGNAL);
std::ostringstream ssSendBuf;
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8 );
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()));
ssSendBuf << svMessage;
int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket,
ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
if (nSendResult == SOCKET_ERROR)
{
Warning(eDLL_T::CLIENT, "Failed to send RCON message: (SOCKET_ERROR)\n");
@ -141,10 +172,11 @@ void CRConClient::Send(const std::string& svMessage) const
//-----------------------------------------------------------------------------
void CRConClient::Recv(void)
{
static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{};
static char szRecvBuf[1024];
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(0);
{//////////////////////////////////////////////
int nPendingLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK);
int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking())
{
return;
@ -158,13 +190,11 @@ void CRConClient::Recv(void)
}//////////////////////////////////////////////
u_long nReadLen; // Find out how much we have to read.
::ioctlsocket(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, FIONREAD, &nReadLen);
::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen);
while (nReadLen > 0)
{
memset(szRecvBuf, '\0', sizeof(szRecvBuf));
int nRecvLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0 && m_bConnEstablished) // Socket was closed.
{
this->Disconnect();
@ -173,50 +203,71 @@ void CRConClient::Recv(void)
}
if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking())
{
Error(eDLL_T::CLIENT, "RCON Cmd: recv error (%s)\n", NET_ErrorString(WSAGetLastError()));
break;
}
nReadLen -= nRecvLen; // Process what we've got.
this->ProcessBuffer(szRecvBuf, nRecvLen);
this->ProcessBuffer(szRecvBuf, nRecvLen, pData);
}
}
//-----------------------------------------------------------------------------
// Purpose: handles input response buffer
// Purpose: parses input response buffer using length-prefix framing
// Input : *pszIn -
// nRecvLen -
// *pData -
//-----------------------------------------------------------------------------
void CRConClient::ProcessBuffer(const char* pszIn, int nRecvLen) const
void CRConClient::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData)
{
int nCharsInRespondBuffer = 0;
char szInputRespondBuffer[MAX_NETCONSOLE_INPUT_LEN]{};
while (nRecvLen)
while (nRecvLen > 0)
{
switch (*pszIn)
if (pData->m_nPayloadLen)
{
case '\r':
{
if (nCharsInRespondBuffer)
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
sv_rcon::response sv_response = this->Deserialize(szInputRespondBuffer);
this->ProcessMessage(sv_response);
}
nCharsInRespondBuffer = 0;
break;
}
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
default:
{
if (nCharsInRespondBuffer < MAX_NETCONSOLE_INPUT_LEN - 1)
{
szInputRespondBuffer[nCharsInRespondBuffer++] = *pszIn;
pRecvBuf++;
nRecvLen--;
}
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
this->ProcessMessage(this->Deserialize(std::string(
reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen)));
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
break;
}
else if (pData->m_nPayloadRead < sizeof(int)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = static_cast<int>(
pData->m_RecvBuffer[0] << 24 |
pData->m_RecvBuffer[1] << 16 |
pData->m_RecvBuffer[2] << 8 |
pData->m_RecvBuffer[3]);
pData->m_nPayloadRead = 0;
if (pData->m_nPayloadLen < 0)
{
Error(eDLL_T::CLIENT, "RCON Cmd: sync error (%d)\n", pData->m_nPayloadLen);
this->Disconnect(); // Out of sync (irrecoverable).
break;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
pszIn++;
nRecvLen--;
}
}
@ -319,7 +370,7 @@ std::string CRConClient::Serialize(const std::string& svReqBuf, const std::strin
break;
}
}
return cl_request.SerializeAsString().append("\r");
return cl_request.SerializeAsString();
}
//-----------------------------------------------------------------------------
@ -330,7 +381,7 @@ std::string CRConClient::Serialize(const std::string& svReqBuf, const std::strin
sv_rcon::response CRConClient::Deserialize(const std::string& svBuf) const
{
sv_rcon::response sv_response;
sv_response.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size()));
sv_response.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size()));
return sv_response;
}

View File

@ -7,8 +7,8 @@
class CRConClient
{
public:
CRConClient(void){};
~CRConClient(void) { delete m_pNetAdr2; delete m_pSocket; };
CRConClient(void);
~CRConClient(void);
void Init(void);
void Shutdown(void);
@ -23,7 +23,7 @@ public:
void Send(const std::string& svMessage) const;
void Recv(void);
void ProcessBuffer(const char* pszIn, int nRecvLen) const;
void ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData);
void ProcessMessage(const sv_rcon::response& sv_response) const;
std::string Serialize(const std::string& svReqBuf, const std::string& svReqVal, cl_rcon::request_t request_t) const;
@ -33,8 +33,8 @@ public:
bool IsConnected(void) const;
private:
CNetAdr2* m_pNetAdr2 = new CNetAdr2("localhost", "37015");
CSocketCreator* m_pSocket = new CSocketCreator();
CNetAdr2* m_pNetAdr2;
CSocketCreator* m_pSocket;
bool m_bInitialized = false;
bool m_bConnEstablished = false;

View File

@ -10,12 +10,25 @@
#include "tier1/IConVar.h"
#include "tier1/NetAdr2.h"
#include "tier2/socketcreator.h"
#include "engine/net.h"
#include "engine/server/sv_rcon.h"
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
#include "mathlib/sha256.h"
#include "common/igameserverdata.h"
//-----------------------------------------------------------------------------
// Purpose: NETCON systems init
//-----------------------------------------------------------------------------
CRConServer::CRConServer()
: m_bInitialized(false)
, m_nConnIndex(0)
{
m_pAdr2 = new CNetAdr2();
m_pSocket = new CSocketCreator();
}
//-----------------------------------------------------------------------------
// Purpose: NETCON systems init
//-----------------------------------------------------------------------------
@ -122,32 +135,70 @@ void CRConServer::RunFrame(void)
}
//-----------------------------------------------------------------------------
// Purpose: send message
// Purpose: send message to all connected sockets
// Input : *svMessage -
//-----------------------------------------------------------------------------
void CRConServer::Send(const std::string& svMessage) const
{
int nCount = m_pSocket->GetAcceptedSocketCount();
for (int i = nCount - 1; i >= 0; i--)
if (int nCount = m_pSocket->GetAcceptedSocketCount())
{
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i);
std::ostringstream ssSendBuf;
if (pData->m_bAuthorized)
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8 );
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()));
ssSendBuf << svMessage;
for (int i = nCount - 1; i >= 0; i--)
{
std::string svFinal = this->Serialize(svMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG);
::send(pData->m_hSocket, svFinal.c_str(), static_cast<int>(svFinal.size()), MSG_NOSIGNAL);
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i);
if (pData->m_bAuthorized)
{
size_t nMsgCount = (ssSendBuf.str().size() + MAX_NETCONSOLE_INPUT_LEN - 1) / MAX_NETCONSOLE_INPUT_LEN;
size_t nDataSize = ssSendBuf.str().size();
size_t nPos = 0;
for (size_t j = 0; j < nMsgCount; j++)
{
size_t nSize = std::min<uint64_t>(MAX_NETCONSOLE_INPUT_LEN, nDataSize);
nDataSize -= nSize;
string svFinal = ssSendBuf.str().substr(nPos, nSize);
::send(pData->m_hSocket, svFinal.data(), static_cast<int>(svFinal.size()), MSG_NOSIGNAL);
nPos += nSize;
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: send message to specific connected socket
// Input : hSocket -
// *svMessage -
//-----------------------------------------------------------------------------
void CRConServer::Send(SocketHandle_t hSocket, const std::string& svMessage) const
{
std::ostringstream ssSendBuf;
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8 );
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()));
ssSendBuf << svMessage;
::send(hSocket, ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
}
//-----------------------------------------------------------------------------
// Purpose: receive message
//-----------------------------------------------------------------------------
void CRConServer::Recv(void)
{
int nCount = m_pSocket->GetAcceptedSocketCount();
static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{};
static char szRecvBuf[1024]{};
for (m_nConnIndex = nCount - 1; m_nConnIndex >= 0; m_nConnIndex--)
{
@ -155,13 +206,12 @@ void CRConServer::Recv(void)
{//////////////////////////////////////////////
if (this->CheckForBan(pData))
{
std::string svNoAuth = this->Serialize(s_pszBannedMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH);
::send(pData->m_hSocket, svNoAuth.c_str(), static_cast<int>(svNoAuth.size()), MSG_NOSIGNAL);
this->Send(pData->m_hSocket, this->Serialize(s_pszBannedMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH));
this->CloseConnection();
continue;
}
int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK);
int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking())
{
continue;
@ -178,9 +228,7 @@ void CRConServer::Recv(void)
while (nReadLen > 0)
{
memset(szRecvBuf, '\0', sizeof(szRecvBuf));
int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0) // Socket was closed.
{
this->CloseConnection();
@ -188,6 +236,7 @@ void CRConServer::Recv(void)
}
if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking())
{
Error(eDLL_T::SERVER, "RCON Cmd: recv error (%s)\n", NET_ErrorString(WSAGetLastError()));
break;
}
@ -229,7 +278,7 @@ std::string CRConServer::Serialize(const std::string& svRspBuf, const std::strin
break;
}
}
return sv_response.SerializeAsString().append("\r");
return sv_response.SerializeAsString();
}
//-----------------------------------------------------------------------------
@ -240,7 +289,7 @@ std::string CRConServer::Serialize(const std::string& svRspBuf, const std::strin
cl_rcon::request CRConServer::Deserialize(const std::string& svBuf) const
{
cl_rcon::request cl_request;
cl_request.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size()));
cl_request.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size()));
return cl_request;
}
@ -249,9 +298,6 @@ cl_rcon::request CRConServer::Deserialize(const std::string& svBuf) const
// Purpose: authenticate new connections
// Input : *cl_request -
// *pData -
// Todo : implement logic for key exchange instead so we never network our
// password in plain text over the wire. create a cvar for this so user could
// also opt out and use legacy authentication instead for older RCON clients
//-----------------------------------------------------------------------------
void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNetConsoleData* pData)
{
@ -259,16 +305,15 @@ void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNet
{
return;
}
else
else // Authorize.
{
if (this->Comparator(cl_request.requestbuf()))
{
pData->m_bAuthorized = true;
m_pSocket->CloseListenSocket();
this->CloseNonAuthConnection();
std::string svAuth = this->Serialize(s_pszAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH);
::send(pData->m_hSocket, svAuth.c_str(), static_cast<int>(svAuth.size()), MSG_NOSIGNAL);
this->CloseNonAuthConnection();
this->Send(pData->m_hSocket, this->Serialize(s_pszAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH));
}
else // Bad password.
{
@ -278,8 +323,7 @@ void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNet
DevMsg(eDLL_T::SERVER, "Bad RCON password attempt from '%s'\n", netAdr2.GetIPAndPort().c_str());
}
std::string svWrongPass = this->Serialize(s_pszWrongPwMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH);
::send(pData->m_hSocket, svWrongPass.c_str(), static_cast<int>(svWrongPass.size()), MSG_NOSIGNAL);
this->Send(pData->m_hSocket, this->Serialize(s_pszWrongPwMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH));
pData->m_bAuthorized = false;
pData->m_bValidated = false;
@ -311,38 +355,70 @@ bool CRConServer::Comparator(std::string svPassword) const
}
//-----------------------------------------------------------------------------
// Purpose: handles input command buffer
// Input : *pszIn -
// Purpose: parses input response buffer using length-prefix framing
// Input : *pRecvBuf -
// nRecvLen -
// *pData -
//-----------------------------------------------------------------------------
void CRConServer::ProcessBuffer(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData)
void CRConServer::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData)
{
while (nRecvLen)
while (nRecvLen > 0)
{
switch (*pszIn)
if (pData->m_nPayloadLen)
{
case '\r':
{
if (pData->m_nCharsInCommandBuffer)
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
cl_rcon::request cl_request = this->Deserialize(pData->m_pszInputCommandBuffer);
this->ProcessMessage(cl_request);
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
pData->m_nCharsInCommandBuffer = 0;
break;
}
default:
{
if (pData->m_nCharsInCommandBuffer < MAX_NETCONSOLE_INPUT_LEN - 1)
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer++] = *pszIn;
this->ProcessMessage(this->Deserialize(std::string(
reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen)));
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
break;
}
else if (pData->m_nPayloadRead < sizeof(int)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = static_cast<int>(
pData->m_RecvBuffer[0] << 24 |
pData->m_RecvBuffer[1] << 16 |
pData->m_RecvBuffer[2] << 8 |
pData->m_RecvBuffer[3]);
pData->m_nPayloadRead = 0;
if (!pData->m_bAuthorized)
{
if (pData->m_nPayloadLen > MAX_NETCONSOLE_INPUT_LEN)
{
this->CloseConnection(); // Sending large messages while not authenticated.
break;
}
}
if (pData->m_nPayloadLen < 0)
{
Error(eDLL_T::SERVER, "RCON Cmd: sync error (%d)\n", pData->m_nPayloadLen);
this->CloseConnection(); // Out of sync (irrecoverable).
break;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
pszIn++;
nRecvLen--;
}
}
@ -358,8 +434,7 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
&& cl_request.requesttype() != cl_rcon::request_t::SERVERDATA_REQUEST_AUTH)
{
// Notify net console that authentication is required.
std::string svMessage = this->Serialize(s_pszNoAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH);
::send(pData->m_hSocket, svMessage.c_str(), static_cast<int>(svMessage.size()), MSG_NOSIGNAL);
this->Send(pData->m_hSocket, this->Serialize(s_pszNoAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH));
pData->m_bValidated = false;
pData->m_nIgnoredMessage++;
@ -373,12 +448,18 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
break;
}
case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND:
{
if (pData->m_bAuthorized) // Only execute if auth was succesfull.
{
this->Execute(cl_request, false);
}
break;
}
case cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE:
{
// Only execute if auth was succesfull.
if (pData->m_bAuthorized)
{
this->Execute(cl_request);
this->Execute(cl_request, true);
}
break;
}
@ -400,15 +481,16 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request)
//-----------------------------------------------------------------------------
// Purpose: execute commands issued from net console
// Input : *cl_request -
// bConVar -
//-----------------------------------------------------------------------------
void CRConServer::Execute(const cl_rcon::request& cl_request) const
void CRConServer::Execute(const cl_rcon::request& cl_request, bool bConVar) const
{
ConVar* pConVar = g_pCVar->FindVar(cl_request.requestbuf().c_str());
if (pConVar)
if (pConVar) // Set value without running the callback.
{
pConVar->SetValue(cl_request.requestval().c_str());
}
else // Execute command with "<val>".
else if (!bConVar) // Execute command with "<val>".
{
Cbuf_AddText(Cbuf_GetCurrentPlayer(), cl_request.requestbuf().c_str(), cmd_source_t::kCommandSrcCode);
Cbuf_Execute();

View File

@ -4,14 +4,15 @@
#include "protoc/sv_rcon.pb.h"
#include "protoc/cl_rcon.pb.h"
constexpr char s_pszNoAuthMessage[] = "This server is password protected for console access. Must send 'PASS <password>' command.\n\r";
constexpr char s_pszWrongPwMessage[] = "Password incorrect.\n\r";
constexpr char s_pszBannedMessage[] = "Go away.\n\r";
constexpr char s_pszAuthMessage[] = "RCON authentication succesfull.\n\r";
constexpr char s_pszNoAuthMessage[] = "This server is password protected for console access. Authenticate with 'PASS <password>' command.\n";
constexpr char s_pszWrongPwMessage[] = "Admin password incorrect.\n";
constexpr char s_pszBannedMessage[] = "Go away.\n";
constexpr char s_pszAuthMessage[] = "RCON authentication successfull.\n";
class CRConServer
{
public:
CRConServer();
~CRConServer() { delete m_pAdr2; delete m_pSocket; }
void Init(void);
@ -22,6 +23,7 @@ public:
void RunFrame(void);
void Send(const std::string& svMessage) const;
void Send(SocketHandle_t hSocket, const std::string& svMessage) const;
void Recv(void);
std::string Serialize(const std::string& svRspBuf, const std::string& svRspVal, sv_rcon::response_t response_t) const;
@ -33,7 +35,7 @@ public:
void ProcessBuffer(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData);
void ProcessMessage(const cl_rcon::request& cl_request);
void Execute(const cl_rcon::request& cl_request) const;
void Execute(const cl_rcon::request& cl_request, bool bConVar) const;
bool CheckForBan(CConnectedNetConsoleData* pData);
void CloseConnection(void);
@ -41,10 +43,10 @@ public:
private:
bool m_bInitialized = false;
int m_nConnIndex = 0;
CNetAdr2* m_pAdr2 = new CNetAdr2();
CSocketCreator* m_pSocket = new CSocketCreator();
bool m_bInitialized;
int m_nConnIndex;
CNetAdr2* m_pAdr2;
CSocketCreator* m_pSocket;
std::vector<std::string> m_vBannedAddress;
std::string m_svPasswordHash;
};

View File

@ -14,6 +14,29 @@
#include "engine/net.h"
#include "netconsole/netconsole.h"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetCon::CNetCon(void)
: m_bInitialized(false)
, m_bNoColor(false)
, m_bQuitApplication(false)
, m_abPromptConnect(true)
, m_abConnEstablished(false)
{
m_pNetAdr2 = new CNetAdr2("localhost", "37015");
m_pSocket = new CSocketCreator();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetCon::~CNetCon(void)
{
delete m_pNetAdr2;
delete m_pSocket;
}
//-----------------------------------------------------------------------------
// Purpose: WSA and NETCON systems init
// Output : true on success, false otherwise
@ -127,9 +150,6 @@ void CNetCon::UserInput(void)
{
if (vSubStrings.size() > 2)
{
printf("%s\n", vSubStrings[1].c_str());
printf("%s\n", vSubStrings[2].c_str());
std::string svSerialized = this->Serialize(vSubStrings[1], vSubStrings[2], cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE);
this->Send(svSerialized);
}
@ -236,8 +256,8 @@ bool CNetCon::Connect(const std::string& svInAdr, const std::string& svInPort)
//-----------------------------------------------------------------------------
void CNetCon::Disconnect(void)
{
::closesocket(m_pSocket->GetAcceptedSocketHandle(0));
m_abPromptConnect = true;
m_pSocket->CloseAcceptedSocket(0);
m_abPromptConnect = true;
m_abConnEstablished = false;
}
@ -247,7 +267,16 @@ void CNetCon::Disconnect(void)
//-----------------------------------------------------------------------------
void CNetCon::Send(const std::string& svMessage) const
{
int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, svMessage.c_str(), svMessage.size(), MSG_NOSIGNAL);
std::ostringstream ssSendBuf;
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16);
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8 );
ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()));
ssSendBuf << svMessage;
int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket,
ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL);
if (nSendResult == SOCKET_ERROR)
{
std::cout << "Failed to send message: (SOCKET_ERROR)" << std::endl;
@ -259,10 +288,11 @@ void CNetCon::Send(const std::string& svMessage) const
//-----------------------------------------------------------------------------
void CNetCon::Recv(void)
{
static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{};
static char szRecvBuf[1024];
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(0);
{//////////////////////////////////////////////
int nPendingLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK);
int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK);
if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking())
{
return;
@ -276,13 +306,11 @@ void CNetCon::Recv(void)
}//////////////////////////////////////////////
u_long nReadLen; // Find out how much we have to read.
::ioctlsocket(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, FIONREAD, &nReadLen);
::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen);
while (nReadLen > 0)
{
memset(szRecvBuf, '\0', sizeof(szRecvBuf));
int nRecvLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL);
if (nRecvLen == 0 && m_abConnEstablished) // Socket was closed.
{
this->Disconnect();
@ -291,50 +319,71 @@ void CNetCon::Recv(void)
}
if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking())
{
std::cout << "RCON Cmd: recv error (" << NET_ErrorString(WSAGetLastError()) << ")" << std::endl;
break;
}
nReadLen -= nRecvLen; // Process what we've got.
this->ProcessBuffer(szRecvBuf, nRecvLen);
this->ProcessBuffer(szRecvBuf, nRecvLen, pData);
}
}
//-----------------------------------------------------------------------------
// Purpose: handles input response buffer
// Input : *pszIn -
// Purpose: parses input response buffer using length-prefix framing
// Input : *pRecvBuf -
// nRecvLen -
// *pData -
//-----------------------------------------------------------------------------
void CNetCon::ProcessBuffer(const char* pszIn, int nRecvLen) const
void CNetCon::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData)
{
int nCharsInRespondBuffer = 0;
char szInputRespondBuffer[MAX_NETCONSOLE_INPUT_LEN]{};
while (nRecvLen)
while (nRecvLen > 0)
{
switch (*pszIn)
if (pData->m_nPayloadLen)
{
case '\r':
{
if (nCharsInRespondBuffer)
if (pData->m_nPayloadRead < pData->m_nPayloadLen)
{
sv_rcon::response sv_response = this->Deserialize(szInputRespondBuffer);
this->ProcessMessage(sv_response);
}
nCharsInRespondBuffer = 0;
break;
}
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
default:
{
if (nCharsInRespondBuffer < MAX_NETCONSOLE_INPUT_LEN - 1)
{
szInputRespondBuffer[nCharsInRespondBuffer++] = *pszIn;
pRecvBuf++;
nRecvLen--;
}
if (pData->m_nPayloadRead == pData->m_nPayloadLen)
{
this->ProcessMessage(this->Deserialize(std::string(
reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen)));
pData->m_nPayloadLen = 0;
pData->m_nPayloadRead = 0;
}
break;
}
else if (pData->m_nPayloadRead < sizeof(int)) // Read size field.
{
pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf;
pRecvBuf++;
nRecvLen--;
}
else // Build prefix.
{
pData->m_nPayloadLen = static_cast<int>(
pData->m_RecvBuffer[0] << 24 |
pData->m_RecvBuffer[1] << 16 |
pData->m_RecvBuffer[2] << 8 |
pData->m_RecvBuffer[3]);
pData->m_nPayloadRead = 0;
if (pData->m_nPayloadLen < 0)
{
std::cout << "RCON Cmd: sync error (" << pData->m_nPayloadLen << ")" << std::endl;
this->Disconnect(); // Out of sync (irrecoverable).
break;
}
else
{
pData->m_RecvBuffer.resize(pData->m_nPayloadLen);
}
}
pszIn++;
nRecvLen--;
}
}
@ -347,25 +396,25 @@ void CNetCon::ProcessMessage(const sv_rcon::response& sv_response) const
static std::regex rxAnsiExp("\\\033\\[.*?m");
switch (sv_response.responsetype())
{
case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH:
case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG:
case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH:
case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG:
{
std::string svOut = sv_response.responsebuf();
if (m_bNoColor)
{
std::string svOut = sv_response.responsebuf();
if (m_bNoColor)
{
svOut = std::regex_replace(svOut, rxAnsiExp, "");
}
else
{
svOut.append(g_svReset.c_str());
}
std::cout << svOut.c_str();
break;
svOut = std::regex_replace(svOut, rxAnsiExp, "");
}
default:
else
{
break;
svOut.append(g_svReset);
}
std::cout << svOut;
break;
}
default:
{
break;
}
}
}
@ -385,20 +434,20 @@ std::string CNetCon::Serialize(const std::string& svReqBuf, const std::string& s
switch (request_t)
{
case cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE:
case cl_rcon::request_t::SERVERDATA_REQUEST_AUTH:
{
cl_request.set_requestbuf(svReqBuf);
cl_request.set_requestval(svReqVal);
break;
}
case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND:
{
cl_request.set_requestbuf(svReqBuf);
break;
}
case cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE:
case cl_rcon::request_t::SERVERDATA_REQUEST_AUTH:
{
cl_request.set_requestbuf(svReqBuf);
cl_request.set_requestval(svReqVal);
break;
}
return cl_request.SerializeAsString().append("\r");
case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND:
{
cl_request.set_requestbuf(svReqBuf);
break;
}
}
return cl_request.SerializeAsString();
}
//-----------------------------------------------------------------------------
@ -409,7 +458,7 @@ std::string CNetCon::Serialize(const std::string& svReqBuf, const std::string& s
sv_rcon::response CNetCon::Deserialize(const std::string& svBuf) const
{
sv_rcon::response sv_response;
sv_response.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size()));
sv_response.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size()));
return sv_response;
}

View File

@ -12,7 +12,8 @@ constexpr const char* NETCON_VERSION = "2.0.0.1";
class CNetCon
{
public:
~CNetCon() { delete m_pNetAdr2; delete m_pSocket; }
CNetCon(void);
~CNetCon(void);
bool Init(void);
bool Shutdown(void);
@ -29,19 +30,19 @@ public:
void Send(const std::string& svMessage) const;
void Recv(void);
void ProcessBuffer(const char* pszIn, int nRecvLen) const;
void ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData);
void ProcessMessage(const sv_rcon::response& sv_response) const;
std::string Serialize(const std::string& svReqBuf, const std::string& svReqVal, cl_rcon::request_t request_t) const;
sv_rcon::response Deserialize(const std::string& svBuf) const;
private:
CNetAdr2* m_pNetAdr2 = new CNetAdr2("localhost", "37015");
CSocketCreator* m_pSocket = new CSocketCreator();
CNetAdr2* m_pNetAdr2;
CSocketCreator* m_pSocket;
bool m_bInitialized = false;
bool m_bNoColor = false;
bool m_bQuitApplication = false;
std::atomic<bool> m_abPromptConnect{ true };
std::atomic<bool> m_abConnEstablished{ false };
bool m_bInitialized;
bool m_bNoColor;
bool m_bQuitApplication;
std::atomic<bool> m_abPromptConnect;
std::atomic<bool> m_abConnEstablished;
};

View File

@ -89,7 +89,7 @@ SQRESULT SQVM_PrintFunc(HSQUIRRELVM v, SQChar* fmt, ...)
{
wconsole->debug(vmStr);
#ifdef DEDICATED
RCONServer()->Send(vmStr);
RCONServer()->Send(RCONServer()->Serialize(vmStr, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
else
@ -135,7 +135,7 @@ SQRESULT SQVM_PrintFunc(HSQUIRRELVM v, SQChar* fmt, ...)
vmStrAnsi.append(buf);
wconsole->debug(vmStrAnsi);
#ifdef DEDICATED
RCONServer()->Send(vmStrAnsi);
RCONServer()->Send(RCONServer()->Serialize(vmStrAnsi, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
@ -229,7 +229,7 @@ SQRESULT SQVM_WarningFunc(HSQUIRRELVM v, SQInteger a2, SQInteger a3, SQInteger*
{
wconsole->debug(vmStr);
#ifdef DEDICATED
RCONServer()->Send(vmStr);
RCONServer()->Send(RCONServer()->Serialize(vmStr, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
else
@ -239,7 +239,7 @@ SQRESULT SQVM_WarningFunc(HSQUIRRELVM v, SQInteger a2, SQInteger a3, SQInteger*
vmStrAnsi.append(svConstructor);
wconsole->debug(vmStrAnsi);
#ifdef DEDICATED
RCONServer()->Send(vmStrAnsi);
RCONServer()->Send(RCONServer()->Serialize(vmStrAnsi, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}

View File

@ -132,7 +132,7 @@ void DevMsg(eDLL_T context, const char* fmt, ...)
{
wconsole->debug(svOut);
#ifdef DEDICATED
RCONServer()->Send(svOut);
RCONServer()->Send(RCONServer()->Serialize(svOut, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
else
@ -147,7 +147,7 @@ void DevMsg(eDLL_T context, const char* fmt, ...)
}
wconsole->debug(svAnsiOut);
#ifdef DEDICATED
RCONServer()->Send(svAnsiOut);
RCONServer()->Send(RCONServer()->Serialize(svAnsiOut, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
@ -240,7 +240,7 @@ void Warning(eDLL_T context, const char* fmt, ...)
{
wconsole->debug(svOut);
#ifdef DEDICATED
RCONServer()->Send(svOut);
RCONServer()->Send(RCONServer()->Serialize(svOut, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
else
@ -256,7 +256,7 @@ void Warning(eDLL_T context, const char* fmt, ...)
}
wconsole->debug(svAnsiOut);
#ifdef DEDICATED
RCONServer()->Send(svAnsiOut);
RCONServer()->Send(RCONServer()->Serialize(svAnsiOut, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
@ -317,7 +317,7 @@ void Error(eDLL_T context, const char* fmt, ...)
{
wconsole->debug(svOut);
#ifdef DEDICATED
RCONServer()->Send(svOut);
RCONServer()->Send(RCONServer()->Serialize(svOut, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}
else
@ -333,7 +333,7 @@ void Error(eDLL_T context, const char* fmt, ...)
}
wconsole->debug(svAnsiOut);
#ifdef DEDICATED
RCONServer()->Send(svAnsiOut);
RCONServer()->Send(RCONServer()->Serialize(svAnsiOut, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG));
#endif // DEDICATED
}

View File

@ -303,6 +303,8 @@ void CSocketCreator::CloseAcceptedSocket(int nIndex)
AcceptedSocket_t& connected = m_hAcceptedSockets[nIndex];
::closesocket(connected.m_hSocket);
delete connected.m_pData;
m_hAcceptedSockets.erase(m_hAcceptedSockets.begin() + nIndex);
}
@ -315,6 +317,8 @@ void CSocketCreator::CloseAllAcceptedSockets(void)
{
AcceptedSocket_t& connected = m_hAcceptedSockets[i];
::closesocket(connected.m_hSocket);
delete connected.m_pData;
}
m_hAcceptedSockets.clear();
}