mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
** SERVER ** * Close redundant connections if max sockets have been reached. * Ban if client keeps spamming without authing first (ignoring message). * Check for whitelisted address before issuing bans (whitelisted address in ConVar 'sv_rcon_whitelist_address' will never get banned or get its connection terminated. * Transmit SQVM and DevMsg logs over the wire to the net console. ** NETCON ** * IPv6 support. * Close connection properly after FIN request. * Prompt user to reconnect after connection has been terminated instead of closing the application. * Add proper quit command. ** SDKLAUNCHER ** * Rename to 'launcher.exe' to describe its purpose better. Our logo gets printed nice and large on the console during startup. ** SDK ** * Cleanup.
326 lines
9.5 KiB
C++
326 lines
9.5 KiB
C++
//===========================================================================//
|
|
//
|
|
// Purpose: Implementation of the rcon server
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "core/stdafx.h"
|
|
#include "tier0/cmd.h"
|
|
#include "tier0/cvar.h"
|
|
#include "tier0/IConVar.h"
|
|
#include "tier1/NetAdr2.h"
|
|
#include "tier2/socketcreator.h"
|
|
#include "engine/sys_utils.h"
|
|
#include "engine/sv_rcon.h"
|
|
#include "mathlib/sha256.h"
|
|
#include "client/IVEngineClient.h"
|
|
#include "common/igameserverdata.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: creates listen socket for RCON
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::Init(void)
|
|
{
|
|
if (std::strlen(rcon_password->GetString()) < 8)
|
|
{
|
|
if (std::strlen(rcon_password->GetString()) != 0)
|
|
{
|
|
DevMsg(eDLL_T::SERVER, "Remote server access requires a password of at least 8 characters\n");
|
|
}
|
|
|
|
if (m_pSocket->IsListening())
|
|
{
|
|
m_pSocket->CloseListenSocket();
|
|
}
|
|
m_bInitialized = false;
|
|
return;
|
|
}
|
|
|
|
static ConVar* hostport = g_pCVar->FindVar("hostport");
|
|
|
|
m_pAdr2 = new CNetAdr2(rcon_address->GetString(), hostport->GetString());
|
|
m_pSocket->CreateListenSocket(*m_pAdr2, false);
|
|
|
|
DevMsg(eDLL_T::SERVER, "Remote server access initialized\n");
|
|
m_bInitialized = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: run tasks for the RCON server
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::Think(void)
|
|
{
|
|
int nCount = m_pSocket->GetAcceptedSocketCount();
|
|
|
|
// Close redundant sockets if there are too many except for whitelisted and authenticated.
|
|
if (nCount >= sv_rcon_maxsockets->GetInt())
|
|
{
|
|
for (m_nConnIndex = nCount - 1; m_nConnIndex >= 0; m_nConnIndex--)
|
|
{
|
|
CNetAdr2 netAdr2 = m_pSocket->GetAcceptedSocketAddress(m_nConnIndex);
|
|
if (std::strcmp(netAdr2.GetIP(true).c_str(), sv_rcon_whitelist_address->GetString()) != 0)
|
|
{
|
|
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(m_nConnIndex);
|
|
if (!pData->m_bAuthorized)
|
|
{
|
|
CloseConnection();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new listen socket if authenticated socket is closed.
|
|
if (nCount == 0)
|
|
{
|
|
if (!m_pSocket->IsListening())
|
|
{
|
|
m_pSocket->CreateListenSocket(*m_pAdr2, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: server RCON main loop (run this every frame)
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::RunFrame(void)
|
|
{
|
|
if (m_bInitialized)
|
|
{
|
|
m_pSocket->RunFrame();
|
|
this->Think();
|
|
this->Recv();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: process outgoing packet
|
|
// Input : *pszBuf -
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::Send(const char* pszBuf)
|
|
{
|
|
int nCount = m_pSocket->GetAcceptedSocketCount();
|
|
|
|
for (int i = nCount - 1; i >= 0; i--)
|
|
{
|
|
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i);
|
|
|
|
if (pData->m_bAuthorized)
|
|
{
|
|
::send(pData->m_hSocket, pszBuf, strlen(pszBuf), MSG_NOSIGNAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: process incoming packet
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::Recv(void)
|
|
{
|
|
int nCount = m_pSocket->GetAcceptedSocketCount();
|
|
|
|
for (m_nConnIndex = nCount - 1; m_nConnIndex >= 0; m_nConnIndex--)
|
|
{
|
|
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(m_nConnIndex);
|
|
|
|
{//////////////////////////////////////////////
|
|
if (CheckForBan(pData))
|
|
{
|
|
::send(pData->m_hSocket, s_pszBannedMessage, strlen(s_pszBannedMessage), MSG_NOSIGNAL);
|
|
CloseConnection();
|
|
continue;
|
|
}
|
|
|
|
char szRecvBuf{};
|
|
int nPendingLen = ::recv(pData->m_hSocket, &szRecvBuf, sizeof(szRecvBuf), MSG_PEEK);
|
|
|
|
if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (nPendingLen <= 0) // EOF or error.
|
|
{
|
|
CloseConnection();
|
|
continue;
|
|
}
|
|
}//////////////////////////////////////////////
|
|
|
|
u_long nReadLen; // Find out how much we have to read.
|
|
::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen);
|
|
|
|
while (nReadLen > 0 && nReadLen < MAX_NETCONSOLE_INPUT_LEN -1)
|
|
{
|
|
char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{};
|
|
int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), 0);
|
|
|
|
if (nRecvLen == 0) // Socket was closed.
|
|
{
|
|
CloseConnection();
|
|
break;
|
|
}
|
|
|
|
if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking())
|
|
{
|
|
break;
|
|
}
|
|
|
|
nReadLen -= nRecvLen;
|
|
|
|
// Write what we've got into the command buffer.
|
|
HandleInputChars(szRecvBuf, nRecvLen, pData);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: authenticate new connections
|
|
// Input : *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::Auth(CConnectedNetConsoleData* pData)
|
|
{
|
|
if (pData->m_bAuthorized)
|
|
{
|
|
return;
|
|
}
|
|
else if (std::memcmp(pData->m_pszInputCommandBuffer, "PASS ", 5) == 0)
|
|
{
|
|
if (std::strcmp(pData->m_pszInputCommandBuffer + 5, rcon_password->GetString()) == 0)
|
|
{ // TODO: Hash and compare password with SHA256 instead!
|
|
pData->m_bAuthorized = true;
|
|
m_pSocket->CloseListenSocket();
|
|
this->CloseNonAuthConnection();
|
|
}
|
|
else // Bad password.
|
|
{
|
|
CNetAdr2 netAdr2 = m_pSocket->GetAcceptedSocketAddress(m_nConnIndex);
|
|
DevMsg(eDLL_T::SERVER, "Bad RCON password attempt from '%s'\n", netAdr2.GetIPAndPort().c_str());
|
|
::send(pData->m_hSocket, s_pszWrongPwMessage, strlen(s_pszWrongPwMessage), MSG_NOSIGNAL);
|
|
|
|
pData->m_bAuthorized = false;
|
|
pData->m_nFailedAttempts++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
::send(pData->m_hSocket, s_pszNoAuthMessage, strlen(s_pszNoAuthMessage), MSG_NOSIGNAL);
|
|
pData->m_nIgnoredMessage++;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: handles input command buffer
|
|
// Input : *pszIn -
|
|
// nRecvLen -
|
|
// *pData -
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::HandleInputChars(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData)
|
|
{
|
|
while (nRecvLen)
|
|
{
|
|
switch (*pszIn)
|
|
{
|
|
case '\r':
|
|
case '\n':
|
|
{
|
|
if (pData->m_nCharsInCommandBuffer)
|
|
{
|
|
pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer] = 0;
|
|
this->Auth(pData);
|
|
|
|
// Only execute if auth was succesfull.
|
|
if (pData->m_bAuthorized)
|
|
{
|
|
this->Execute(pData);
|
|
}
|
|
}
|
|
pData->m_nCharsInCommandBuffer = 0;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
if (pData->m_nCharsInCommandBuffer < MAX_NETCONSOLE_INPUT_LEN - 1)
|
|
{
|
|
pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer++] = *pszIn;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
pszIn++;
|
|
nRecvLen--;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: execute commands issued from net console
|
|
// Input : *pData -
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::Execute(CConnectedNetConsoleData* pData)
|
|
{
|
|
IVEngineClient_CommandExecute(NULL, pData->m_pszInputCommandBuffer);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: checks for amount of failed attempts and bans net console accordingly
|
|
// Input : *pData -
|
|
//-----------------------------------------------------------------------------
|
|
bool CRConServer::CheckForBan(CConnectedNetConsoleData* pData)
|
|
{
|
|
CNetAdr2 netAdr2 = m_pSocket->GetAcceptedSocketAddress(m_nConnIndex);
|
|
|
|
// Check if IP is in the ban vector.
|
|
if (std::find(m_vBannedAddress.begin(), m_vBannedAddress.end(),
|
|
netAdr2.GetIP(true)) != m_vBannedAddress.end())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Check if net console has reached maximum number of attempts and add to ban vector.
|
|
if (pData->m_nFailedAttempts >= sv_rcon_maxfailures->GetInt()
|
|
|| pData->m_nIgnoredMessage >= sv_rcon_maxignores->GetInt())
|
|
{
|
|
// Don't add whitelisted address to ban vector.
|
|
if (std::strcmp(netAdr2.GetIP(true).c_str(), sv_rcon_whitelist_address->GetString()) == 0)
|
|
{
|
|
pData->m_nFailedAttempts = 0;
|
|
pData->m_nIgnoredMessage = 0;
|
|
return false;
|
|
}
|
|
|
|
DevMsg(eDLL_T::SERVER, "Banned '%s' for RCON hacking attempts\n", netAdr2.GetIPAndPort().c_str());
|
|
m_vBannedAddress.push_back(netAdr2.GetIP(true));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: close specific connection
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::CloseConnection(void) // NETMGR
|
|
{
|
|
m_pSocket->CloseAcceptedSocket(m_nConnIndex);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: close all connections except for authenticated
|
|
//-----------------------------------------------------------------------------
|
|
void CRConServer::CloseNonAuthConnection(void)
|
|
{
|
|
int nCount = m_pSocket->GetAcceptedSocketCount();
|
|
|
|
for (int i = nCount - 1; i >= 0; i--)
|
|
{
|
|
CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i);
|
|
|
|
if (!pData->m_bAuthorized)
|
|
{
|
|
m_pSocket->CloseAcceptedSocket(i);
|
|
}
|
|
}
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
CRConServer* g_pRConServer = new CRConServer();
|