2022-02-06 16:48:52 +01:00
//===========================================================================//
2022-02-13 15:10:38 +01:00
//
// Purpose: Implementation of the rcon server.
//
2022-02-06 16:48:52 +01:00
//===========================================================================//
# include "core/stdafx.h"
2022-04-09 16:16:40 +02:00
# include "tier1/cvar.h"
2023-01-29 15:24:24 +01:00
# include "tier1/NetAdr.h"
2022-02-06 16:48:52 +01:00
# include "tier2/socketcreator.h"
2023-05-10 00:05:38 +02:00
# include "engine/cmd.h"
2022-08-02 23:58:43 +02:00
# include "engine/net.h"
2022-05-20 11:52:19 +02:00
# include "engine/server/sv_rcon.h"
2022-02-13 15:10:38 +01:00
# include "protoc/sv_rcon.pb.h"
# include "protoc/cl_rcon.pb.h"
2022-02-06 16:48:52 +01:00
# include "common/igameserverdata.h"
2024-02-22 01:55:08 +01:00
# include "mbedtls/include/mbedtls/sha512.h"
2022-02-06 16:48:52 +01:00
2023-04-19 01:35:31 +02:00
//-----------------------------------------------------------------------------
2024-02-24 02:15:09 +01:00
// Purpose: constants
2023-04-19 01:35:31 +02:00
//-----------------------------------------------------------------------------
static const char s_NoAuthMessage [ ] = " This server is password protected for console access; authenticate with 'PASS <password>' command. \n " ;
static const char s_WrongPwMessage [ ] = " Admin password incorrect. \n " ;
static const char s_AuthMessage [ ] = " Authentication successful. \n " ;
static const char s_BannedMessage [ ] = " Go away. \n " ;
2022-08-02 23:58:43 +02:00
2024-02-24 02:15:09 +01:00
//-----------------------------------------------------------------------------
// Purpose: console variables
//-----------------------------------------------------------------------------
static void RCON_WhiteListAddresChanged_f ( IConVar * pConVar , const char * pOldString ) ;
static void RCON_ConnectionCountChanged_f ( IConVar * pConVar , const char * pOldString ) ;
static void RCON_PasswordChanged_f ( IConVar * pConVar , const char * pOldString ) ;
static ConVar rcon_password ( " rcon_password " , " " , FCVAR_SERVER_CANNOT_QUERY | FCVAR_DONTRECORD | FCVAR_RELEASE , " Remote server access password (rcon is disabled if empty) " , false, 0.f, false, 0.f, &RCON_PasswordChanged_f) ;
static ConVar sv_rcon_debug ( " sv_rcon_debug " , " 0 " , FCVAR_RELEASE , " Show rcon debug information ( !slower! ) " ) ;
static ConVar sv_rcon_sendlogs ( " sv_rcon_sendlogs " , " 0 " , FCVAR_RELEASE , " Network console logs to connected and authenticated sockets " ) ;
//static ConVar sv_rcon_banpenalty("sv_rcon_banpenalty" , "10", FCVAR_RELEASE, "Number of minutes to ban users who fail rcon authentication");
static ConVar sv_rcon_maxfailures ( " sv_rcon_maxfailures " , " 10 " , FCVAR_RELEASE , " Max number of times an user can fail rcon authentication before being banned " , true , 1.f , false , 0.f ) ;
static ConVar sv_rcon_maxignores ( " sv_rcon_maxignores " , " 15 " , FCVAR_RELEASE , " Max number of times an user can ignore the instruction message before being banned " , true , 1.f , false , 0.f ) ;
static ConVar sv_rcon_maxsockets ( " sv_rcon_maxsockets " , " 32 " , FCVAR_RELEASE , " Max number of accepted sockets before the server starts closing redundant sockets " , true , 1.f , true , MAX_PLAYERS ) ;
static ConVar sv_rcon_maxconnections ( " sv_rcon_maxconnections " , " 1 " , FCVAR_RELEASE , " Max number of authenticated connections before the server closes the listen socket " , true , 1.f , true , MAX_PLAYERS , & RCON_ConnectionCountChanged_f ) ;
static ConVar sv_rcon_maxpacketsize ( " sv_rcon_maxpacketsize " , " 1024 " , FCVAR_RELEASE , " Max number of bytes allowed in a command packet from a non-authenticated netconsole " , true , 0.f , false , 0.f ) ;
static ConVar sv_rcon_whitelist_address ( " sv_rcon_whitelist_address " , " " , FCVAR_RELEASE , " This address is not considered a 'redundant' socket and will never be banned for failed authentication attempts " , & RCON_WhiteListAddresChanged_f , " Format: '::ffff:127.0.0.1' " ) ;
2022-08-02 23:58:43 +02:00
//-----------------------------------------------------------------------------
2022-08-03 09:32:48 +02:00
// Purpose:
2022-08-02 23:58:43 +02:00
//-----------------------------------------------------------------------------
2022-08-03 09:32:48 +02:00
CRConServer : : CRConServer ( void )
2023-04-19 01:35:31 +02:00
: m_nConnIndex ( 0 )
, m_nAuthConnections ( 0 )
, m_bInitialized ( false )
2022-08-02 23:58:43 +02:00
{
}
2022-08-03 09:32:48 +02:00
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CRConServer : : ~ CRConServer ( void )
{
2024-02-22 01:55:08 +01:00
// NOTE: do not call Shutdown() from the destructor as the OS's socket
// system would be shutdown by now, call Shutdown() in application
// shutdown code instead
2022-08-03 09:32:48 +02:00
}
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2022-02-14 23:16:24 +01:00
// Purpose: NETCON systems init
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
void CRConServer : : Init ( void )
{
2022-07-25 19:35:08 +02:00
if ( ! m_bInitialized )
2022-02-06 16:48:52 +01:00
{
2024-02-24 02:15:09 +01:00
if ( ! SetPassword ( rcon_password . GetString ( ) ) )
2022-02-08 16:32:00 +01:00
{
2022-07-25 19:35:08 +02:00
return ;
2022-02-08 16:32:00 +01:00
}
2022-02-06 16:48:52 +01:00
}
2023-04-30 11:32:51 +02:00
else
{
// Already initialized.
return ;
}
const char * pszAddress = net_usesocketsforloopback - > GetBool ( ) ? NET_IPV6_UNSPEC : NET_IPV6_LOOPBACK ;
2022-02-06 16:48:52 +01:00
2023-04-30 11:32:51 +02:00
m_Address . SetFromString ( Format ( " [%s]:%i " , pszAddress , hostport - > GetInt ( ) ) . c_str ( ) , true ) ;
2023-04-16 00:24:06 +02:00
m_Socket . CreateListenSocket ( m_Address ) ;
2022-02-06 16:48:52 +01:00
2023-08-21 19:12:29 +02:00
Msg ( eDLL_T : : SERVER , " Remote server access initialized ('%s') \n " , m_Address . ToString ( ) ) ;
2022-02-06 16:48:52 +01:00
m_bInitialized = true ;
}
2022-02-14 23:16:24 +01:00
//-----------------------------------------------------------------------------
// Purpose: NETCON systems shutdown
//-----------------------------------------------------------------------------
void CRConServer : : Shutdown ( void )
{
2024-02-22 01:55:08 +01:00
m_bInitialized = false ;
const int nConnCount = m_Socket . GetAcceptedSocketCount ( ) ;
2023-04-16 17:51:48 +02:00
m_Socket . CloseAllAcceptedSockets ( ) ;
2024-02-22 01:55:08 +01:00
2022-11-14 21:00:41 +01:00
if ( m_Socket . IsListening ( ) )
2022-02-14 23:16:24 +01:00
{
2022-11-14 21:00:41 +01:00
m_Socket . CloseListenSocket ( ) ;
2022-02-14 23:16:24 +01:00
}
2023-04-16 17:51:48 +02:00
2024-02-22 01:55:08 +01:00
Msg ( eDLL_T : : SERVER , " Remote server access deinitialized ('%i' accepted sockets closed) \n " , nConnCount ) ;
2022-02-14 23:16:24 +01:00
}
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2022-02-08 16:32:00 +01:00
// Purpose: run tasks for the RCON server
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
void CRConServer : : Think ( void )
{
2022-11-14 21:00:41 +01:00
const int nCount = m_Socket . GetAcceptedSocketCount ( ) ;
2022-02-08 16:32:00 +01:00
// Close redundant sockets if there are too many except for whitelisted and authenticated.
2024-02-24 02:15:09 +01:00
if ( nCount > sv_rcon_maxsockets . GetInt ( ) )
2022-02-08 16:32:00 +01:00
{
for ( m_nConnIndex = nCount - 1 ; m_nConnIndex > = 0 ; m_nConnIndex - - )
{
2023-01-29 15:24:24 +01:00
const netadr_t & netAdr = m_Socket . GetAcceptedSocketAddress ( m_nConnIndex ) ;
2023-04-16 17:51:48 +02:00
if ( ! m_WhiteListAddress . CompareAdr ( netAdr ) )
2022-02-08 16:32:00 +01:00
{
2023-08-04 17:41:55 +02:00
const CConnectedNetConsoleData & data = m_Socket . GetAcceptedSocketData ( m_nConnIndex ) ;
if ( ! data . m_bAuthorized )
2022-02-08 16:32:00 +01:00
{
2023-04-22 16:02:54 +02:00
Disconnect ( " redundant " ) ;
2022-02-08 16:32:00 +01:00
}
}
}
}
2022-02-13 15:10:38 +01:00
// Create a new listen socket if authenticated connection is closed.
2022-02-08 16:32:00 +01:00
if ( nCount = = 0 )
{
2022-11-14 21:00:41 +01:00
if ( ! m_Socket . IsListening ( ) )
2022-02-08 16:32:00 +01:00
{
2023-04-16 00:24:06 +02:00
m_Socket . CreateListenSocket ( m_Address ) ;
2022-02-08 16:32:00 +01:00
}
}
2022-02-06 16:48:52 +01:00
}
2022-07-25 19:35:08 +02:00
//-----------------------------------------------------------------------------
// Purpose: changes the password
// Input : *pszPassword -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CRConServer : : SetPassword ( const char * pszPassword )
{
2023-04-22 16:02:54 +02:00
const size_t nLen = strlen ( pszPassword ) ;
2023-04-16 17:51:48 +02:00
if ( nLen < RCON_MIN_PASSWORD_LEN )
2022-07-25 19:35:08 +02:00
{
2023-04-16 17:51:48 +02:00
if ( nLen > NULL )
2022-07-25 19:35:08 +02:00
{
2023-04-16 17:51:48 +02:00
Warning ( eDLL_T : : SERVER , " Remote server access requires a password of at least %i characters \n " ,
RCON_MIN_PASSWORD_LEN ) ;
2022-07-25 19:35:08 +02:00
}
2022-08-11 11:07:45 +02:00
2023-04-19 01:35:31 +02:00
Shutdown ( ) ;
2022-07-25 19:35:08 +02:00
return false ;
}
2022-08-11 11:07:45 +02:00
2024-02-22 01:55:08 +01:00
// This is here so we only print the confirmation message if the user
// actually requested to change the password rather than initializing
// the RCON server
const bool wasInitialized = m_bInitialized ;
m_bInitialized = false ;
m_Socket . CloseAllAcceptedSockets ( ) ;
const int nHashRet = mbedtls_sha512 ( reinterpret_cast < const uint8_t * > ( pszPassword ) , nLen , m_PasswordHash , NULL ) ;
if ( nHashRet ! = 0 )
{
Error ( eDLL_T : : SERVER , 0 , " SHA-512 algorithm failed on RCON password [%i] \n " , nHashRet ) ;
return false ;
}
if ( wasInitialized )
{
Msg ( eDLL_T : : SERVER , " Successfully changed RCON server password \n " ) ;
}
2022-07-25 19:35:08 +02:00
m_bInitialized = true ;
return true ;
}
2023-04-16 17:51:48 +02:00
//-----------------------------------------------------------------------------
// Purpose: sets the white list address
// Input : *pszAddress -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool CRConServer : : SetWhiteListAddress ( const char * pszAddress )
{
return m_WhiteListAddress . SetFromString ( pszAddress ) ;
}
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
// Purpose: server RCON main loop (run this every frame)
//-----------------------------------------------------------------------------
void CRConServer : : RunFrame ( void )
{
if ( m_bInitialized )
{
2022-11-14 21:00:41 +01:00
m_Socket . RunFrame ( ) ;
2023-04-19 01:35:31 +02:00
Think ( ) ;
const int nCount = m_Socket . GetAcceptedSocketCount ( ) ;
for ( m_nConnIndex = nCount - 1 ; m_nConnIndex > = 0 ; m_nConnIndex - - )
{
2023-08-04 17:41:55 +02:00
CConnectedNetConsoleData & data = m_Socket . GetAcceptedSocketData ( m_nConnIndex ) ;
2023-04-19 01:35:31 +02:00
2023-08-04 17:41:55 +02:00
if ( CheckForBan ( data ) )
2023-04-19 01:35:31 +02:00
{
2023-08-04 17:41:55 +02:00
SendEncode ( data . m_hSocket , s_BannedMessage , " " ,
2023-04-19 01:35:31 +02:00
sv_rcon : : response_t : : SERVERDATA_RESPONSE_AUTH , int ( eDLL_T : : NETCON ) ) ;
2023-04-22 16:02:54 +02:00
Disconnect ( " banned " ) ;
2023-04-19 01:35:31 +02:00
continue ;
}
2024-02-24 02:15:09 +01:00
Recv ( data , sv_rcon_maxpacketsize . GetInt ( ) ) ;
2023-04-19 01:35:31 +02:00
}
2022-02-06 16:48:52 +01:00
}
}
//-----------------------------------------------------------------------------
2022-08-02 23:58:43 +02:00
// Purpose: send message to all connected sockets
2023-04-19 01:35:31 +02:00
// Input : *pMsgBuf -
// nMsgLen -
// Output: true on success, false otherwise
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2023-04-22 16:02:54 +02:00
bool CRConServer : : SendToAll ( const char * pMsgBuf , const int nMsgLen ) const
2022-02-06 16:48:52 +01:00
{
2023-04-22 16:02:54 +02:00
ostringstream sendbuf ;
2023-04-19 01:35:31 +02:00
const u_long nLen = htonl ( u_long ( nMsgLen ) ) ;
2022-02-06 16:48:52 +01:00
2023-04-19 01:35:31 +02:00
bool bSuccess = true ;
sendbuf . write ( reinterpret_cast < const char * > ( & nLen ) , sizeof ( u_long ) ) ;
sendbuf . write ( pMsgBuf , nMsgLen ) ;
2022-08-02 23:58:43 +02:00
2023-03-13 21:20:20 +01:00
const int nCount = m_Socket . GetAcceptedSocketCount ( ) ;
for ( int i = nCount - 1 ; i > = 0 ; i - - )
{
2023-08-04 17:41:55 +02:00
const CConnectedNetConsoleData & data = m_Socket . GetAcceptedSocketData ( i ) ;
2022-08-02 23:58:43 +02:00
2023-08-04 17:41:55 +02:00
if ( data . m_bAuthorized & & ! data . m_bInputOnly )
2023-03-13 21:20:20 +01:00
{
2023-08-04 17:41:55 +02:00
int ret = : : send ( data . m_hSocket , sendbuf . str ( ) . data ( ) ,
2023-04-19 01:35:31 +02:00
int ( sendbuf . str ( ) . size ( ) ) , MSG_NOSIGNAL ) ;
if ( ret = = SOCKET_ERROR )
{
if ( ! bSuccess )
{
bSuccess = false ;
}
}
2022-02-08 16:32:00 +01:00
}
}
2022-08-02 23:58:43 +02:00
2023-04-19 01:35:31 +02:00
return bSuccess ;
2022-08-02 23:58:43 +02:00
}
2022-08-17 02:20:04 +02:00
//-----------------------------------------------------------------------------
2023-04-19 01:35:31 +02:00
// Purpose: encode and send message to all connected sockets
// Input : *pResponseMsg -
// *pResponseVal -
2022-08-17 02:20:04 +02:00
// responseType -
2023-03-27 02:01:48 +02:00
// nMessageId -
// nMessageType -
2023-04-19 01:35:31 +02:00
// Output: true on success, false otherwise
2022-08-17 02:20:04 +02:00
//-----------------------------------------------------------------------------
2023-04-19 01:35:31 +02:00
bool CRConServer : : SendEncode ( const char * pResponseMsg , const char * pResponseVal ,
const sv_rcon : : response_t responseType , const int nMessageId , const int nMessageType ) const
2022-08-17 02:20:04 +02:00
{
2023-04-19 01:35:31 +02:00
vector < char > vecMsg ;
if ( ! Serialize ( vecMsg , pResponseMsg , pResponseVal ,
responseType , nMessageId , nMessageType ) )
2022-08-17 02:20:04 +02:00
{
2023-04-19 01:35:31 +02:00
return false ;
}
if ( ! SendToAll ( vecMsg . data ( ) , int ( vecMsg . size ( ) ) ) )
{
Error ( eDLL_T : : SERVER , NO_ERROR , " Failed to send RCON message: (%s) \n " , " SOCKET_ERROR " ) ;
return false ;
2022-08-17 02:20:04 +02:00
}
2023-04-19 01:35:31 +02:00
return true ;
2022-08-17 02:20:04 +02:00
}
//-----------------------------------------------------------------------------
2023-04-19 01:35:31 +02:00
// Purpose: encode and send message to specific socket
2022-08-17 02:20:04 +02:00
// Input : hSocket -
2023-04-19 01:35:31 +02:00
// *pResponseMsg -
// *pResponseVal -
2022-08-17 02:20:04 +02:00
// responseType -
2023-03-27 02:01:48 +02:00
// nMessageId -
// nMessageType -
2023-04-19 01:35:31 +02:00
// Output: true on success, false otherwise
2022-08-17 02:20:04 +02:00
//-----------------------------------------------------------------------------
2023-04-19 01:35:31 +02:00
bool CRConServer : : SendEncode ( const SocketHandle_t hSocket , const char * pResponseMsg , const char * pResponseVal ,
const sv_rcon : : response_t responseType , const int nMessageId , const int nMessageType ) const
2022-08-17 02:20:04 +02:00
{
2023-04-19 01:35:31 +02:00
vector < char > vecMsg ;
if ( ! Serialize ( vecMsg , pResponseMsg , pResponseVal ,
responseType , nMessageId , nMessageType ) )
2023-02-04 01:34:08 +01:00
{
2023-04-19 01:35:31 +02:00
return false ;
2023-02-04 01:34:08 +01:00
}
2023-04-19 01:35:31 +02:00
if ( ! Send ( hSocket , vecMsg . data ( ) , int ( vecMsg . size ( ) ) ) )
2022-02-08 16:32:00 +01:00
{
2023-04-19 01:35:31 +02:00
Error ( eDLL_T : : SERVER , NO_ERROR , " Failed to send RCON message: (%s) \n " , " SOCKET_ERROR " ) ;
return false ;
2022-02-13 15:10:38 +01:00
}
2023-04-19 01:35:31 +02:00
return true ;
2022-02-13 15:10:38 +01:00
}
//-----------------------------------------------------------------------------
// Purpose: serializes input
2023-04-22 16:02:54 +02:00
// Input : &vecBuf -
// *responseMsg -
2023-03-27 02:01:48 +02:00
// *responseVal -
2022-08-17 02:20:04 +02:00
// responseType -
2023-03-27 02:01:48 +02:00
// nMessageId -
// nMessageType -
2022-02-13 15:10:38 +01:00
// Output : serialized results as string
//-----------------------------------------------------------------------------
2023-04-19 01:35:31 +02:00
bool CRConServer : : Serialize ( vector < char > & vecBuf , const char * pResponseMsg , const char * pResponseVal ,
2023-03-27 02:01:48 +02:00
const sv_rcon : : response_t responseType , const int nMessageId , const int nMessageType ) const
2022-02-13 15:10:38 +01:00
{
2023-04-19 01:35:31 +02:00
sv_rcon : : response response ;
response . set_messageid ( nMessageId ) ;
response . set_messagetype ( nMessageType ) ;
response . set_responsetype ( responseType ) ;
response . set_responsemsg ( pResponseMsg ) ;
response . set_responseval ( pResponseVal ) ;
2022-02-06 16:48:52 +01:00
2023-04-19 01:35:31 +02:00
const size_t msgLen = response . ByteSizeLong ( ) ;
vecBuf . resize ( msgLen ) ;
2022-02-13 15:10:38 +01:00
2023-04-19 01:35:31 +02:00
if ( ! Encode ( & response , & vecBuf [ 0 ] , msgLen ) )
2022-02-13 15:10:38 +01:00
{
2023-04-19 01:35:31 +02:00
Error ( eDLL_T : : SERVER , NO_ERROR , " Failed to encode RCON buffer \n " ) ;
return false ;
2022-02-06 16:48:52 +01:00
}
2022-02-13 15:10:38 +01:00
2023-04-19 01:35:31 +02:00
return true ;
2022-02-06 16:48:52 +01:00
}
//-----------------------------------------------------------------------------
// Purpose: authenticate new connections
2023-04-22 16:02:54 +02:00
// Input : &request -
2023-08-04 17:41:55 +02:00
// &data -
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2023-08-04 17:41:55 +02:00
void CRConServer : : Authenticate ( const cl_rcon : : request & request , CConnectedNetConsoleData & data )
2022-02-06 16:48:52 +01:00
{
2023-08-04 17:41:55 +02:00
if ( data . m_bAuthorized )
2022-02-06 16:48:52 +01:00
{
return ;
}
2022-08-02 23:58:43 +02:00
else // Authorize.
2022-02-06 16:48:52 +01:00
{
2023-04-22 16:02:54 +02:00
if ( Comparator ( request . requestmsg ( ) ) )
2022-02-14 03:02:38 +01:00
{
2023-08-04 17:41:55 +02:00
data . m_bAuthorized = true ;
2024-02-24 02:15:09 +01:00
if ( + + m_nAuthConnections > = sv_rcon_maxconnections . GetInt ( ) )
2023-04-19 01:35:31 +02:00
{
m_Socket . CloseListenSocket ( ) ;
CloseNonAuthConnection ( ) ;
}
2022-02-24 16:44:33 +01:00
2024-02-24 02:15:09 +01:00
const char * pSendLogs = ( ! sv_rcon_sendlogs . GetBool ( ) | | data . m_bInputOnly ) ? " 0 " : " 1 " ;
2023-08-04 17:28:01 +02:00
2023-08-04 17:41:55 +02:00
SendEncode ( data . m_hSocket , s_AuthMessage , pSendLogs ,
2023-04-19 01:35:31 +02:00
sv_rcon : : response_t : : SERVERDATA_RESPONSE_AUTH , static_cast < int > ( eDLL_T : : NETCON ) ) ;
2022-02-06 16:48:52 +01:00
}
else // Bad password.
{
2024-01-01 20:09:32 +01:00
const netadr_t & netAdr = m_Socket . GetAcceptedSocketAddress ( m_nConnIndex ) ;
2024-02-24 02:15:09 +01:00
if ( sv_rcon_debug . GetBool ( ) )
2022-02-14 03:02:38 +01:00
{
2023-08-21 19:12:29 +02:00
Msg ( eDLL_T : : SERVER , " Bad RCON password attempt from '%s' \n " , netAdr . ToString ( ) ) ;
2022-02-14 03:02:38 +01:00
}
2022-02-13 15:10:38 +01:00
2023-08-04 17:41:55 +02:00
SendEncode ( data . m_hSocket , s_WrongPwMessage , " " ,
2023-04-19 01:35:31 +02:00
sv_rcon : : response_t : : SERVERDATA_RESPONSE_AUTH , static_cast < int > ( eDLL_T : : NETCON ) ) ;
2022-02-06 16:48:52 +01:00
2023-08-04 17:41:55 +02:00
data . m_bAuthorized = false ;
data . m_bValidated = false ;
data . m_nFailedAttempts + + ;
2022-02-06 16:48:52 +01:00
}
}
}
2022-02-14 03:02:38 +01:00
//-----------------------------------------------------------------------------
2024-02-22 01:55:08 +01:00
// Purpose: sha512 hashed password comparison
2023-04-22 16:02:54 +02:00
// Input : &svPassword -
2022-02-14 03:02:38 +01:00
// Output : true if matches, false otherwise
//-----------------------------------------------------------------------------
2023-04-22 16:02:54 +02:00
bool CRConServer : : Comparator ( const string & svPassword ) const
2022-02-14 03:02:38 +01:00
{
2024-02-22 01:55:08 +01:00
uint8_t clientPasswordHash [ RCON_SHA512_HASH_SIZE ] ;
mbedtls_sha512 ( reinterpret_cast < const uint8_t * > ( svPassword . c_str ( ) ) , svPassword . length ( ) ,
clientPasswordHash , NULL ) ;
if ( memcmp ( clientPasswordHash , m_PasswordHash , RCON_SHA512_HASH_SIZE ) = = 0 )
2022-02-14 03:02:38 +01:00
{
return true ;
}
2024-02-22 01:55:08 +01:00
2022-02-14 03:02:38 +01:00
return false ;
}
2022-02-13 15:10:38 +01:00
//-----------------------------------------------------------------------------
// Purpose: processes received message
2023-04-19 01:35:31 +02:00
// Input : *pMsgBuf -
// nMsgLen -
// Output : true on success, false otherwise
2022-02-13 15:10:38 +01:00
//-----------------------------------------------------------------------------
2023-04-22 16:02:54 +02:00
bool CRConServer : : ProcessMessage ( const char * pMsgBuf , const int nMsgLen )
2022-02-13 15:10:38 +01:00
{
2023-08-04 17:41:55 +02:00
CConnectedNetConsoleData & data = m_Socket . GetAcceptedSocketData ( m_nConnIndex ) ;
2022-02-13 15:10:38 +01:00
2023-04-19 01:35:31 +02:00
cl_rcon : : request request ;
if ( ! Decode ( & request , pMsgBuf , nMsgLen ) )
{
Error ( eDLL_T : : SERVER , NO_ERROR , " Failed to decode RCON buffer \n " ) ;
return false ;
}
2023-08-04 17:41:55 +02:00
if ( ! data . m_bAuthorized & &
2023-04-19 01:35:31 +02:00
request . requesttype ( ) ! = cl_rcon : : request_t : : SERVERDATA_REQUEST_AUTH )
2022-02-13 15:10:38 +01:00
{
2023-05-15 18:00:51 +02:00
// Notify netconsole that authentication is required.
2023-08-04 17:41:55 +02:00
SendEncode ( data . m_hSocket , s_NoAuthMessage , " " ,
2023-04-19 01:35:31 +02:00
sv_rcon : : response_t : : SERVERDATA_RESPONSE_AUTH , static_cast < int > ( eDLL_T : : NETCON ) ) ;
2022-02-13 15:10:38 +01:00
2023-08-04 17:41:55 +02:00
data . m_bValidated = false ;
data . m_nIgnoredMessage + + ;
2023-04-19 01:35:31 +02:00
return true ;
2022-02-13 15:10:38 +01:00
}
2023-04-19 01:35:31 +02:00
switch ( request . requesttype ( ) )
2022-02-13 15:10:38 +01:00
{
case cl_rcon : : request_t : : SERVERDATA_REQUEST_AUTH :
{
2023-08-04 17:41:55 +02:00
Authenticate ( request , data ) ;
2022-02-13 15:10:38 +01:00
break ;
}
case cl_rcon : : request_t : : SERVERDATA_REQUEST_EXECCOMMAND :
2022-08-02 23:58:43 +02:00
{
2023-08-04 17:41:55 +02:00
if ( data . m_bAuthorized ) // Only execute if auth was successful.
2022-08-02 23:58:43 +02:00
{
2023-08-04 11:20:24 +02:00
Execute ( request ) ;
2022-02-13 15:10:38 +01:00
}
break ;
}
case cl_rcon : : request_t : : SERVERDATA_REQUEST_SEND_CONSOLE_LOG :
{
2023-08-04 17:41:55 +02:00
if ( data . m_bAuthorized )
2022-02-13 15:10:38 +01:00
{
2023-08-05 01:11:32 +02:00
// request value "0" means the netconsole is input only.
const bool bWantLog = atoi ( request . requestval ( ) . c_str ( ) ) ! = NULL ;
data . m_bInputOnly = ! bWantLog ;
2024-02-24 02:15:09 +01:00
if ( bWantLog & & ! sv_rcon_sendlogs . GetBool ( ) )
2023-08-05 01:11:32 +02:00
{
// Toggle it on since there's at least 1 netconsole that
// wants to receive logs.
2024-02-24 02:15:09 +01:00
sv_rcon_sendlogs . SetValue ( bWantLog ) ;
2023-08-05 01:11:32 +02:00
}
2022-02-13 15:10:38 +01:00
}
break ;
}
default :
{
break ;
}
}
2023-04-19 01:35:31 +02:00
return true ;
2022-02-13 15:10:38 +01:00
}
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2023-08-04 11:53:46 +02:00
// Purpose: execute commands issued from netconsole (ignores all protection flags)
// Input : &request -
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2023-08-04 11:20:24 +02:00
void CRConServer : : Execute ( const cl_rcon : : request & request ) const
2022-02-06 16:48:52 +01:00
{
2023-08-05 20:29:07 +02:00
const string & commandString = request . requestmsg ( ) . c_str ( ) ;
const char * const pCommandString = commandString . c_str ( ) ;
2023-08-04 11:53:46 +02:00
ConCommandBase * pCommandBase = g_pCVar - > FindCommandBase ( pCommandString ) ;
if ( ! pCommandBase )
{
// Found nothing.
return ;
}
2023-08-05 20:29:07 +02:00
const char * const pValueString = request . requestval ( ) . c_str ( ) ;
2023-08-04 11:53:46 +02:00
2023-08-05 20:29:07 +02:00
if ( ! pCommandBase - > IsCommand ( ) )
2022-02-13 15:10:38 +01:00
{
2023-08-05 20:29:07 +02:00
// Here we want to skip over the command string in the value buffer.
// So if we got 'sv_cheats 1' in our value buffer, we want to skip
// over 'sv_cheats ', so that we are pointing directly to the value.
const char * pFound = V_strstr ( pValueString , pCommandString ) ;
const char * pValue = nullptr ;
if ( pFound )
{
pValue = pFound + commandString . length ( ) ;
// Skip any leading space characters.
while ( * pValue = = ' ' )
{
+ + pValue ;
}
}
2023-08-04 11:53:46 +02:00
ConVar * pConVar = reinterpret_cast < ConVar * > ( pCommandBase ) ;
2023-08-05 20:29:07 +02:00
pConVar - > SetValue ( pValue ? pValue : pValueString ) ;
2022-02-13 15:10:38 +01:00
}
2023-08-04 11:53:46 +02:00
else // Invoke command callback directly.
2022-02-13 15:10:38 +01:00
{
2023-08-04 11:53:46 +02:00
CCommand cmd ;
2024-02-22 01:55:08 +01:00
// Only tokenize if we actually have strings in the value buffer, some
// commands (like 'status') don't need any additional parameters.
if ( VALID_CHARSTAR ( pValueString ) )
{
cmd . Tokenize ( pValueString , cmd_source_t : : kCommandSrcCode ) ;
}
2023-08-04 11:53:46 +02:00
v_Cmd_Dispatch ( ECommandTarget_t : : CBUF_SERVER , pCommandBase , & cmd , false ) ;
2022-02-13 15:10:38 +01:00
}
2022-02-06 16:48:52 +01:00
}
//-----------------------------------------------------------------------------
2023-05-15 18:00:51 +02:00
// Purpose: checks for amount of failed attempts and bans netconsole accordingly
2023-08-04 17:41:55 +02:00
// Input : &data -
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2023-08-04 17:41:55 +02:00
bool CRConServer : : CheckForBan ( CConnectedNetConsoleData & data )
2022-02-06 16:48:52 +01:00
{
2023-08-04 17:41:55 +02:00
if ( data . m_bValidated )
2022-05-13 21:41:03 +02:00
{
return false ;
}
2024-01-01 20:09:32 +01:00
const netadr_t & netAdr = m_Socket . GetAcceptedSocketAddress ( m_nConnIndex ) ;
2023-04-16 17:51:48 +02:00
const char * szNetAdr = netAdr . ToString ( true ) ;
if ( m_BannedList . size ( ) > = RCON_MAX_BANNEDLIST_SIZE )
{
2024-02-24 02:15:09 +01:00
const char * pszWhiteListAddress = sv_rcon_whitelist_address . GetString ( ) ;
2023-04-16 17:51:48 +02:00
if ( ! pszWhiteListAddress [ 0 ] )
{
2023-04-19 01:35:31 +02:00
Warning ( eDLL_T : : SERVER , " Banned list overflowed; please use a whitelist address. RCON shutting down... \n " ) ;
Shutdown ( ) ;
2023-04-16 17:51:48 +02:00
return true ;
}
// Only allow whitelisted at this point.
if ( ! m_WhiteListAddress . CompareAdr ( netAdr ) )
{
2024-02-24 02:15:09 +01:00
if ( sv_rcon_debug . GetBool ( ) )
2023-04-16 17:51:48 +02:00
{
2023-08-21 19:12:29 +02:00
Msg ( eDLL_T : : SERVER , " Banned list is full; dropping '%s' \n " , szNetAdr ) ;
2023-04-16 17:51:48 +02:00
}
return true ;
}
}
2023-08-04 17:41:55 +02:00
data . m_bValidated = true ;
2022-02-06 16:48:52 +01:00
2022-11-14 21:00:41 +01:00
// Check if IP is in the banned list.
2023-04-16 17:51:48 +02:00
if ( m_BannedList . find ( szNetAdr ) ! = m_BannedList . end ( ) )
2022-02-06 16:48:52 +01:00
{
return true ;
}
2023-05-15 18:00:51 +02:00
// Check if netconsole has reached maximum number of attempts > add to banned list.
2024-02-24 02:15:09 +01:00
if ( data . m_nFailedAttempts > = sv_rcon_maxfailures . GetInt ( )
| | data . m_nIgnoredMessage > = sv_rcon_maxignores . GetInt ( ) )
2022-02-06 16:48:52 +01:00
{
2022-11-14 21:00:41 +01:00
// Don't add white listed address to banned list.
2023-04-19 01:35:31 +02:00
if ( m_WhiteListAddress . CompareAdr ( netAdr ) )
2022-02-08 16:32:00 +01:00
{
2023-08-04 17:41:55 +02:00
data . m_nFailedAttempts = 0 ;
data . m_nIgnoredMessage = 0 ;
2022-02-08 16:32:00 +01:00
return false ;
}
2023-04-19 01:35:31 +02:00
Warning ( eDLL_T : : SERVER , " Banned '%s' for RCON hacking attempts \n " , szNetAdr ) ;
2023-04-16 17:51:48 +02:00
m_BannedList . insert ( szNetAdr ) ;
2023-04-19 01:35:31 +02:00
2022-02-06 16:48:52 +01:00
return true ;
}
2023-04-19 01:35:31 +02:00
2022-02-06 16:48:52 +01:00
return false ;
}
//-----------------------------------------------------------------------------
2023-04-22 16:51:18 +02:00
// Purpose: close connection on current index
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
2023-04-22 16:02:54 +02:00
void CRConServer : : Disconnect ( const char * szReason ) // NETMGR
2022-02-06 16:48:52 +01:00
{
2023-04-22 16:51:18 +02:00
Disconnect ( m_nConnIndex , szReason ) ;
}
//-----------------------------------------------------------------------------
// Purpose: close specific connection by index
//-----------------------------------------------------------------------------
void CRConServer : : Disconnect ( const int nIndex , const char * szReason ) // NETMGR
{
2023-08-04 17:41:55 +02:00
CConnectedNetConsoleData & data = m_Socket . GetAcceptedSocketData ( nIndex ) ;
2023-09-17 17:19:32 +02:00
if ( data . m_bAuthorized )
2022-02-24 16:44:33 +01:00
{
// Inform server owner when authenticated connection has been closed.
2024-01-01 20:09:32 +01:00
const netadr_t & netAdr = m_Socket . GetAcceptedSocketAddress ( nIndex ) ;
2023-04-22 16:02:54 +02:00
if ( ! szReason )
{
szReason = " unknown reason " ;
}
2023-04-19 01:35:31 +02:00
2023-08-21 19:12:29 +02:00
Msg ( eDLL_T : : SERVER , " Connection to '%s' lost (%s) \n " , netAdr . ToString ( ) , szReason ) ;
2023-04-19 01:35:31 +02:00
m_nAuthConnections - - ;
2022-02-24 16:44:33 +01:00
}
2023-04-19 01:35:31 +02:00
2023-04-22 16:51:18 +02:00
m_Socket . CloseAcceptedSocket ( nIndex ) ;
2022-02-06 16:48:52 +01:00
}
2022-02-08 16:32:00 +01:00
//-----------------------------------------------------------------------------
// Purpose: close all connections except for authenticated
//-----------------------------------------------------------------------------
void CRConServer : : CloseNonAuthConnection ( void )
{
2022-11-14 21:00:41 +01:00
int nCount = m_Socket . GetAcceptedSocketCount ( ) ;
2022-02-08 16:32:00 +01:00
for ( int i = nCount - 1 ; i > = 0 ; i - - )
{
2023-08-04 17:41:55 +02:00
CConnectedNetConsoleData & data = m_Socket . GetAcceptedSocketData ( i ) ;
2022-02-08 16:32:00 +01:00
2023-08-04 17:41:55 +02:00
if ( ! data . m_bAuthorized )
2022-02-08 16:32:00 +01:00
{
2022-11-14 21:00:41 +01:00
m_Socket . CloseAcceptedSocket ( i ) ;
2022-02-08 16:32:00 +01:00
}
}
}
2022-08-11 11:07:45 +02:00
2023-03-13 21:20:20 +01:00
//-----------------------------------------------------------------------------
// Purpose: checks if this message should be send or not
2023-04-19 01:35:31 +02:00
// Input : responseType -
2023-03-13 21:20:20 +01:00
// Output : true if it should send, false otherwise
//-----------------------------------------------------------------------------
bool CRConServer : : ShouldSend ( const sv_rcon : : response_t responseType ) const
{
2023-04-19 01:35:31 +02:00
if ( ! IsInitialized ( ) | | ! m_Socket . GetAcceptedSocketCount ( ) )
2023-03-13 21:20:20 +01:00
{
// Not initialized or no sockets...
return false ;
}
if ( responseType = = sv_rcon : : response_t : : SERVERDATA_RESPONSE_CONSOLE_LOG )
{
2024-02-24 02:15:09 +01:00
if ( ! sv_rcon_sendlogs . GetBool ( ) | | ! m_Socket . GetAuthorizedSocketCount ( ) )
2023-03-13 21:20:20 +01:00
{
// Disabled or no authorized clients to send to...
return false ;
}
}
return true ;
}
2022-08-11 11:07:45 +02:00
//-----------------------------------------------------------------------------
2023-04-19 01:35:31 +02:00
// Purpose: returns whether the rcon server is initialized
2022-08-11 11:07:45 +02:00
//-----------------------------------------------------------------------------
bool CRConServer : : IsInitialized ( void ) const
{
return m_bInitialized ;
}
2023-04-22 16:51:18 +02:00
//-----------------------------------------------------------------------------
// Purpose: returns the number of authenticated connections
//-----------------------------------------------------------------------------
int CRConServer : : GetAuthenticatedCount ( void ) const
{
return m_nAuthConnections ;
}
2024-02-24 02:15:09 +01:00
//-----------------------------------------------------------------------------
// Purpose: change whitelist address on RCON server
//-----------------------------------------------------------------------------
static void RCON_WhiteListAddresChanged_f ( IConVar * pConVar , const char * pOldString )
{
if ( ConVar * pConVarRef = g_pCVar - > FindVar ( pConVar - > GetName ( ) ) )
{
if ( strcmp ( pOldString , pConVarRef - > GetString ( ) ) = = NULL )
return ; // Same address.
if ( ! RCONServer ( ) - > SetWhiteListAddress ( pConVarRef - > GetString ( ) ) )
{
Warning ( eDLL_T : : SERVER , " Failed to set RCON whitelist address: %s \n " , pConVarRef - > GetString ( ) ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: change max connection count on RCON server
//-----------------------------------------------------------------------------
static void RCON_ConnectionCountChanged_f ( IConVar * pConVar , const char * pOldString )
{
if ( ! RCONServer ( ) - > IsInitialized ( ) )
return ; // Not initialized; no sockets at this point.
if ( ConVar * pConVarRef = g_pCVar - > FindVar ( pConVar - > GetName ( ) ) )
{
if ( strcmp ( pOldString , pConVarRef - > GetString ( ) ) = = NULL )
return ; // Same count.
const int maxCount = pConVarRef - > GetInt ( ) ;
int count = RCONServer ( ) - > GetAuthenticatedCount ( ) ;
CSocketCreator * pCreator = RCONServer ( ) - > GetSocketCreator ( ) ;
if ( count < maxCount )
{
if ( ! pCreator - > IsListening ( ) )
{
pCreator - > CreateListenSocket ( * RCONServer ( ) - > GetNetAddress ( ) ) ;
}
}
else
{
while ( count > maxCount )
{
RCONServer ( ) - > Disconnect ( count - 1 , " too many authenticated sockets " ) ;
count = RCONServer ( ) - > GetAuthenticatedCount ( ) ;
}
pCreator - > CloseListenSocket ( ) ;
RCONServer ( ) - > CloseNonAuthConnection ( ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: change RCON password on server and drop all connections
//-----------------------------------------------------------------------------
void RCON_PasswordChanged_f ( IConVar * pConVar , const char * pOldString )
{
if ( ConVar * pConVarRef = g_pCVar - > FindVar ( pConVar - > GetName ( ) ) )
{
if ( strcmp ( pOldString , pConVarRef - > GetString ( ) ) = = NULL )
return ; // Same password.
if ( RCONServer ( ) - > IsInitialized ( ) )
RCONServer ( ) - > SetPassword ( pConVarRef - > GetString ( ) ) ;
else
RCONServer ( ) - > Init ( ) ; // Initialize first.
}
}
2022-02-08 16:32:00 +01:00
///////////////////////////////////////////////////////////////////////////////
2024-02-22 01:55:08 +01:00
static CRConServer s_RCONServer ;
2022-11-14 21:00:41 +01:00
CRConServer * RCONServer ( ) // Singleton RCON Server.
2022-07-25 19:35:08 +02:00
{
2024-02-22 01:55:08 +01:00
return & s_RCONServer ;
2022-07-25 19:35:08 +02:00
}