333 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
// RCONServer.cpp
 | 
						|
 | 
						|
// Implements the cRCONServer class representing the RCON server
 | 
						|
 | 
						|
#include "Globals.h"
 | 
						|
#include "../iniFile/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,
 | 
						|
} ;
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
						|
// 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, 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),
 | 
						|
	m_ListenThread4(*this, cSocket::IPv4, "RCON"),
 | 
						|
	m_ListenThread6(*this, cSocket::IPv6, "RCON")
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
cRCONServer::~cRCONServer()
 | 
						|
{
 | 
						|
	m_ListenThread4.Stop();
 | 
						|
	m_ListenThread6.Stop();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
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 and initialize both IPv4 and IPv6 ports for RCON
 | 
						|
	bool HasAnyPorts = false;
 | 
						|
	AString Ports4 = a_IniFile.GetValueSet("RCON", "PortsIPv4", "25575");
 | 
						|
	if (m_ListenThread4.Initialize(Ports4))
 | 
						|
	{
 | 
						|
		HasAnyPorts = true;
 | 
						|
		m_ListenThread4.Start();
 | 
						|
	}
 | 
						|
	AString Ports6 = a_IniFile.GetValueSet("RCON", "PortsIPv6", "25575");
 | 
						|
	if (m_ListenThread6.Initialize(Ports6))
 | 
						|
	{
 | 
						|
		HasAnyPorts = true;
 | 
						|
		m_ListenThread6.Start();
 | 
						|
	}
 | 
						|
	if (!HasAnyPorts)
 | 
						|
	{
 | 
						|
		LOGWARNING("RCON is requested, but no ports are specified. Specify at least one port in PortsIPv4 or PortsIPv6. RCON is now disabled.");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void cRCONServer::OnConnectionAccepted(cSocket & a_Socket)
 | 
						|
{
 | 
						|
	if (!a_Socket.IsValid())
 | 
						|
	{
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	LOG("RCON Client \"%s\" connected!", a_Socket.GetIPString().c_str());
 | 
						|
	
 | 
						|
	// Create a new cConnection object, it will be deleted when the connection is closed
 | 
						|
	m_SocketThreads.AddClient(a_Socket, new cConnection(*this, a_Socket));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
						|
// cRCONServer::cConnection:
 | 
						|
 | 
						|
cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket) :
 | 
						|
	m_IsAuthenticated(false),
 | 
						|
	m_RCONServer(a_RCONServer),
 | 
						|
	m_Socket(a_Socket),
 | 
						|
	m_IPAddress(a_Socket.GetIPString())
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void cRCONServer::cConnection::DataReceived(const char * a_Data, int a_Size)
 | 
						|
{
 | 
						|
	// 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_RCONServer.m_SocketThreads.RemoveClient(this);
 | 
						|
			m_Socket.CloseSocket();
 | 
						|
			delete this;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (Length > (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_RCONServer.m_SocketThreads.RemoveClient(this);
 | 
						|
			m_Socket.CloseSocket();
 | 
						|
			delete this;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		m_Buffer.erase(0, Length + 4);
 | 
						|
	}  // while (m_Buffer.size() >= 14)
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void cRCONServer::cConnection::GetOutgoingData(AString & a_Data)
 | 
						|
{
 | 
						|
	a_Data.assign(m_Outgoing);
 | 
						|
	m_Outgoing.clear();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void cRCONServer::cConnection::SocketClosed(void)
 | 
						|
{
 | 
						|
	m_RCONServer.m_SocketThreads.RemoveClient(this);
 | 
						|
	delete this;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
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());
 | 
						|
				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, NULL);
 | 
						|
			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, NULL);
 | 
						|
			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 != NULL));  // Either zero data to send, or a valid payload ptr
 | 
						|
	
 | 
						|
	char Buffer[4];
 | 
						|
	int Length = a_PayloadLength + 10;
 | 
						|
	IntToBuffer(Length, Buffer);
 | 
						|
	m_Outgoing.append(Buffer, 4);
 | 
						|
	IntToBuffer(a_RequestID, Buffer);
 | 
						|
	m_Outgoing.append(Buffer, 4);
 | 
						|
	IntToBuffer(a_PacketType, Buffer);
 | 
						|
	m_Outgoing.append(Buffer, 4);
 | 
						|
	if (a_PayloadLength > 0)
 | 
						|
	{
 | 
						|
		m_Outgoing.append(a_Payload, a_PayloadLength);
 | 
						|
	}
 | 
						|
	m_Outgoing.push_back(0);
 | 
						|
	m_Outgoing.push_back(0);
 | 
						|
	m_RCONServer.m_SocketThreads.NotifyWrite(this);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 |