summaryrefslogblamecommitdiffstats
path: root/src/RCONServer.cpp
blob: 2ec7f191f0977996de264dcb0ec0f41e46dea60b (plain) (tree)
1
2
3
4
5
6
7





                                                                
                    
































                                                                                       
                                                                                




































                                                                                                                             



















                                                                                      
                                                                                                                     












                                                
                                                                                


                                              
                          








                           



                                       




















                                                                                                    




                                                                                                                 
         










                                                                                                                
         

                                    
         
                                                                                                     






         

                                                                                
 




                                                                                                





 
                                                                
 
                        





 
                                                                                 
 

                                  












                                                                                                                  


                                        
                 
                                                                   

                                                                            
                               





                                                                                             


                                        








                                              
                                                   
 
                       





 
                                                                                   
 

                                                                                                           














                                                                                                                            
                                                                                   






                                                                                                       
                                                                                    
















                                                                                                                
                                                                                    








































                                                                                                                                                        
                                                                                                                      
                                  



                                          
                                
                                         
                                
                                          
                                

                                
                                                         
         
                                                                     




 

// RCONServer.cpp

// Implements the cRCONServer class representing the RCON server

#include "Globals.h"
#include "IniFile.h"
#include "RCONServer.h"
#include "Server.h"
#include "Root.h"
#include "CommandOutput.h"





// Disable MSVC warnings:
#if defined(_MSC_VER)
	#pragma warning(push)
	#pragma warning(disable:4355)  // 'this' : used in base member initializer list
#endif





enum
{
	// Client -> Server:
	RCON_PACKET_COMMAND = 2,
	RCON_PACKET_LOGIN   = 3,
	
	// Server -> Client:
	RCON_PACKET_RESPONSE = 2,
} ;





////////////////////////////////////////////////////////////////////////////////
// cRCONListenCallbacks:

class cRCONListenCallbacks:
	public cNetwork::cListenCallbacks
{
public:
	cRCONListenCallbacks(cRCONServer & a_RCONServer, UInt16 a_Port):
		m_RCONServer(a_RCONServer),
		m_Port(a_Port)
	{
	}

protected:
	/** The RCON server instance that we're attached to. */
	cRCONServer & m_RCONServer;

	/** The port for which this instance is responsible. */
	UInt16 m_Port;

	// cNetwork::cListenCallbacks overrides:
	virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
	{
		LOG("RCON Client \"%s\" connected!", a_RemoteIPAddress.c_str());
		return std::make_shared<cRCONServer::cConnection>(m_RCONServer, a_RemoteIPAddress);
	}
	virtual void OnAccepted(cTCPLink & a_Link) override {}
	virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
	{
		LOGWARNING("RCON server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg);
	}
};





////////////////////////////////////////////////////////////////////////////////
// cRCONCommandOutput:

class cRCONCommandOutput :
	public cCommandOutputCallback
{
public:
	cRCONCommandOutput(cRCONServer::cConnection & a_Connection, int a_RequestID) :
		m_Connection(a_Connection),
		m_RequestID(a_RequestID)
	{
	}
	
	// cCommandOutputCallback overrides:
	virtual void Out(const AString & a_Text) override
	{
		m_Buffer.append(a_Text);
	}
	
	virtual void Finished(void) override
	{
		m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, (int)m_Buffer.size(), m_Buffer.c_str());
		delete this;
	}
	
protected:
	cRCONServer::cConnection & m_Connection;
	int m_RequestID;
	AString m_Buffer;
} ;





////////////////////////////////////////////////////////////////////////////////
// cRCONServer:

cRCONServer::cRCONServer(cServer & a_Server) :
	m_Server(a_Server)
{
}





cRCONServer::~cRCONServer()
{
	for (auto srv: m_ListenServers)
	{
		srv->Close();
	}
}





void cRCONServer::Initialize(cIniFile & a_IniFile)
{
	if (!a_IniFile.GetValueSetB("RCON", "Enabled", false))
	{
		return;
	}

	// Read the password, don't allow an empty one:
	m_Password = a_IniFile.GetValueSet("RCON", "Password", "");
	if (m_Password.empty())
	{
		LOGWARNING("RCON is requested, but the password is not set. RCON is now disabled.");
		return;
	}
	
	// Read the listening ports for RCON from config:
	AStringVector Ports = ReadUpgradeIniPorts(a_IniFile, "RCON", "Ports", "PortsIPv4", "PortsIPv6", "25575");

	// Start listening on each specified port:
	for (auto port: Ports)
	{
		UInt16 PortNum = static_cast<UInt16>(atol(port.c_str()));
		if (PortNum == 0)
		{
			LOGINFO("Invalid RCON port value: \"%s\". Ignoring.", port.c_str());
			continue;
		}
		auto Handle = cNetwork::Listen(PortNum, std::make_shared<cRCONListenCallbacks>(*this, PortNum));
		if (Handle->IsListening())
		{
			m_ListenServers.push_back(Handle);
		}
	}

	if (m_ListenServers.empty())
	{
		LOGWARNING("RCON is enabled but no valid ports were found. RCON is not accessible.");
	}
}





////////////////////////////////////////////////////////////////////////////////
// cRCONServer::cConnection:

cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress) :
	m_IsAuthenticated(false),
	m_RCONServer(a_RCONServer),
	m_IPAddress(a_IPAddress)
{
}





void cRCONServer::cConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
	m_Link = a_Link;
}





void cRCONServer::cConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
	ASSERT(m_Link != nullptr);

	// Append data to the buffer:
	m_Buffer.append(a_Data, a_Size);
	
	// Process the packets in the buffer:
	while (m_Buffer.size() >= 14)
	{
		int Length = IntFromBuffer(m_Buffer.data());
		if (Length > 1500)
		{
			// Too long, drop the connection
			LOGWARNING("Received an invalid RCON packet length (%d), dropping RCON connection to %s.",
				Length, m_IPAddress.c_str()
			);
			m_Link->Close();
			m_Link.reset();
			return;
		}
		if (Length > static_cast<int>(m_Buffer.size() + 4))
		{
			// Incomplete packet yet, wait for more data to come
			return;
		}
		
		int RequestID  = IntFromBuffer(m_Buffer.data() + 4);
		int PacketType = IntFromBuffer(m_Buffer.data() + 8);
		if (!ProcessPacket(RequestID, PacketType, Length - 10, m_Buffer.data() + 12))
		{
			m_Link->Close();
			m_Link.reset();
			return;
		}
		m_Buffer.erase(0, Length + 4);
	}  // while (m_Buffer.size() >= 14)
}





void cRCONServer::cConnection::OnRemoteClosed(void)
{
	m_Link.reset();
}





void cRCONServer::cConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
	LOGD("Error in RCON connection %s: %d (%s)", m_IPAddress.c_str(), a_ErrorCode, a_ErrorMsg.c_str());
	m_Link.reset();
}





bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
{
	switch (a_PacketType)
	{
		case RCON_PACKET_LOGIN:
		{
			if (strncmp(a_Payload, m_RCONServer.m_Password.c_str(), a_PayloadLength) != 0)
			{
				LOGINFO("RCON: Invalid password from client %s, dropping connection.", m_IPAddress.c_str());
				SendResponse(-1, RCON_PACKET_RESPONSE, 0, nullptr);
				return false;
			}
			m_IsAuthenticated = true;
			
			LOGD("RCON: Client at %s has successfully authenticated", m_IPAddress.c_str());
			
			// Send OK response:
			SendResponse(a_RequestID, RCON_PACKET_RESPONSE, 0, nullptr);
			return true;
		}
		
		case RCON_PACKET_COMMAND:
		{
			if (!m_IsAuthenticated)
			{
				char AuthNeeded[] = "You need to authenticate first!";
				SendResponse(a_RequestID, RCON_PACKET_RESPONSE, sizeof(AuthNeeded), AuthNeeded);
				return false;
			}
			
			AString cmd(a_Payload, a_PayloadLength);
			LOGD("RCON command from %s: \"%s\"", m_IPAddress.c_str(), cmd.c_str());
			cRoot::Get()->ExecuteConsoleCommand(cmd, *(new cRCONCommandOutput(*this, a_RequestID)));
			
			// Send an empty response:
			SendResponse(a_RequestID, RCON_PACKET_RESPONSE, 0, nullptr);
			return true;
		}
	}
	
	// Unknown packet type, drop the connection:
	LOGWARNING("RCON: Client at %s has sent an unknown packet type %d, dropping connection.",
		m_IPAddress.c_str(), a_PacketType
	);
	return false;
}





/// Reads 4 bytes from a_Buffer and returns the int they represent
int cRCONServer::cConnection::IntFromBuffer(const char * a_Buffer)
{
	return ((unsigned char)a_Buffer[3] << 24) | ((unsigned char)a_Buffer[2] << 16) | ((unsigned char)a_Buffer[1] << 8) | (unsigned char)a_Buffer[0];
}





/// Puts 4 bytes representing the int into the buffer
void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
{
	a_Buffer[0] = a_Value & 0xff;
	a_Buffer[1] = (a_Value >> 8)  & 0xff;
	a_Buffer[2] = (a_Value >> 16) & 0xff;
	a_Buffer[3] = (a_Value >> 24) & 0xff;
}





/// Sends a RCON packet back to the client
void cRCONServer::cConnection::SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
{
	ASSERT((a_PayloadLength == 0) || (a_Payload != nullptr));  // Either zero data to send, or a valid payload ptr
	ASSERT(m_Link != nullptr);
	
	char Buffer[4];
	int Length = a_PayloadLength + 10;
	IntToBuffer(Length, Buffer);
	m_Link->Send(Buffer, 4);
	IntToBuffer(a_RequestID, Buffer);
	m_Link->Send(Buffer, 4);
	IntToBuffer(a_PacketType, Buffer);
	m_Link->Send(Buffer, 4);
	if (a_PayloadLength > 0)
	{
		m_Link->Send(a_Payload, a_PayloadLength);
	}
	m_Link->Send("\0", 2);  // Send two zero chars as the padding
}