diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index 904c0bd3b..d1e1a5e14 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -610,6 +610,14 @@ RelativePath="..\source\ProbabDistrib.h" > + + + + diff --git a/source/OSSupport/ListenThread.cpp b/source/OSSupport/ListenThread.cpp index bc14aa0ba..70c1159a4 100644 --- a/source/OSSupport/ListenThread.cpp +++ b/source/OSSupport/ListenThread.cpp @@ -10,10 +10,11 @@ -cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family) : - super("ListenThread"), +cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName) : + super(Printf("ListenThread %s", a_ServiceName.c_str())), m_Callback(a_Callback), - m_Family(a_Family) + m_Family(a_Family), + m_ServiceName(a_ServiceName) { } @@ -105,11 +106,11 @@ bool cListenThread::CreateSockets(const AString & a_PortsString) return false; } - const char * FamilyStr = ""; + AString FamilyStr = m_ServiceName; switch (m_Family) { - case cSocket::IPv4: FamilyStr = "IPv4"; break; - case cSocket::IPv6: FamilyStr = "IPv6"; break; + case cSocket::IPv4: FamilyStr.append(" IPv4"); break; + case cSocket::IPv6: FamilyStr.append(" IPv6"); break; default: { ASSERT(!"Unknown address family"); @@ -122,13 +123,13 @@ bool cListenThread::CreateSockets(const AString & a_PortsString) int Port = atoi(itr->c_str()); if ((Port <= 0) || (Port > 65535)) { - LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr, itr->c_str()); + LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr.c_str(), itr->c_str()); continue; } m_Sockets.push_back(cSocket::CreateSocket(m_Family)); if (!m_Sockets.back().IsValid()) { - LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr, Port, cSocket::GetLastErrorString().c_str()); + LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); m_Sockets.pop_back(); continue; } @@ -137,7 +138,7 @@ bool cListenThread::CreateSockets(const AString & a_PortsString) { if (!m_Sockets.back().SetReuseAddress()) { - LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr, Port, cSocket::GetLastErrorString().c_str()); + LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); } } @@ -155,19 +156,19 @@ bool cListenThread::CreateSockets(const AString & a_PortsString) } if (!res) { - LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr, Port, cSocket::GetLastErrorString().c_str()); + LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); m_Sockets.pop_back(); continue; } if (!m_Sockets.back().Listen()) { - LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr, Port, cSocket::GetLastErrorString().c_str()); + LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str()); m_Sockets.pop_back(); continue; } - LOGINFO("%s: Port %d is open for connections", FamilyStr, Port); + LOGINFO("%s: Port %d is open for connections", FamilyStr.c_str(), Port); } // for itr - Ports[] return !(m_Sockets.empty()); diff --git a/source/OSSupport/ListenThread.h b/source/OSSupport/ListenThread.h index 952cc8a3f..c29140ed3 100644 --- a/source/OSSupport/ListenThread.h +++ b/source/OSSupport/ListenThread.h @@ -37,7 +37,7 @@ public: virtual void OnConnectionAccepted(cSocket & a_Socket) = 0; } ; - cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family); + cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName = ""); ~cListenThread(); /// Creates all the sockets, returns trus if successful, false if not. @@ -62,8 +62,13 @@ protected: /// Sockets that are being monitored cSockets m_Sockets; + /// If set to true, the SO_REUSEADDR socket option is set to true bool m_ShouldReuseAddr; + /// Name of the service that's listening on the ports; for logging purposes only + AString m_ServiceName; + + /** Fills in m_Sockets with individual sockets, each for one port specified in a_PortsString. Returns true if successful and at least one socket has been created */ diff --git a/source/RCONServer.cpp b/source/RCONServer.cpp new file mode 100644 index 000000000..9558512eb --- /dev/null +++ b/source/RCONServer.cpp @@ -0,0 +1,289 @@ + +// 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" + + + + + +// 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, +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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: + { + AString cmd(a_Payload, a_PayloadLength); + LOGD("RCON command from %s: \"%s\"", m_IPAddress.c_str(), cmd.c_str()); + cRoot::Get()->ExecuteConsoleCommand(cmd); + + // 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); +} + + + + diff --git a/source/RCONServer.h b/source/RCONServer.h new file mode 100644 index 000000000..95122ba5f --- /dev/null +++ b/source/RCONServer.h @@ -0,0 +1,106 @@ + +// RCONServer.h + +// Declares the cRCONServer class representing the RCON server + + + + + +#pragma once + +#include "OSSupport/SocketThreads.h" +#include "OSSupport/ListenThread.h" + + + + + +// fwd: +class cServer; +class cIniFile; + + + + + +class cRCONServer : + public cListenThread::cCallback +{ +public: + cRCONServer(cServer & a_Server); + ~cRCONServer(); + + void Initialize(cIniFile & a_IniFile); + +protected: + class cConnection : + public cSocketThreads::cCallback + { + public: + cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket); + + protected: + + /// Set to true if the client has successfully authenticated + bool m_IsAuthenticated; + + /// Buffer for the incoming data + AString m_Buffer; + + /// Buffer for the outgoing data + AString m_Outgoing; + + /// Server that owns this connection and processes requests + cRCONServer & m_RCONServer; + + /// The socket belonging to the client + cSocket & m_Socket; + + /// Address of the client + AString m_IPAddress; + + + // cSocketThreads::cCallback overrides: + virtual void DataReceived(const char * a_Data, int a_Size) override; + virtual void GetOutgoingData(AString & a_Data) override; + virtual void SocketClosed(void) override; + + /// Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped + bool ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload); + + /// Reads 4 bytes from a_Buffer and returns the int they represent + int IntFromBuffer(const char * a_Buffer); + + /// Puts 4 bytes representing the int into the buffer + void IntToBuffer(int a_Value, char * a_Buffer); + + /// Sends a RCON packet back to the client + void SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload); + } ; + + + /// The server object that will process the commands received + cServer & m_Server; + + /// The thread(s) that take care of all the traffic on the RCON ports + cSocketThreads m_SocketThreads; + + /// The thread for accepting IPv4 RCON connections + cListenThread m_ListenThread4; + + /// The thread for accepting IPv6 RCON connections + cListenThread m_ListenThread6; + + /// Password for authentication + AString m_Password; + + + // cListenThread::cCallback overrides: + virtual void OnConnectionAccepted(cSocket & a_Socket) override; +} ; + + + + + diff --git a/source/Server.cpp b/source/Server.cpp index 21fbb97db..7dac3ea18 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -174,6 +174,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) return false; } + m_RCONServer.Initialize(a_SettingsIni); + m_bIsConnected = true; m_pState->ServerID = "-"; @@ -215,12 +217,13 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) cServer::cServer(void) : m_pState(new sServerState) - , m_ListenThreadIPv4(*this, cSocket::IPv4) - , m_ListenThreadIPv6(*this, cSocket::IPv6) + , m_ListenThreadIPv4(*this, cSocket::IPv4, "Client") + , m_ListenThreadIPv6(*this, cSocket::IPv6, "Client") , m_Millisecondsf(0) , m_Milliseconds(0) , m_bIsConnected(false) , m_bRestarting(false) + , m_RCONServer(*this) { } diff --git a/source/Server.h b/source/Server.h index dfda56c62..3f7a24699 100644 --- a/source/Server.h +++ b/source/Server.h @@ -8,13 +8,12 @@ #pragma once -#ifndef CSERVER_H_INCLUDED -#define CSERVER_H_INCLUDED #include "OSSupport/SocketThreads.h" #include "OSSupport/ListenThread.h" #include "CryptoPP/rsa.h" #include "CryptoPP/randpool.h" +#include "RCONServer.h" @@ -105,11 +104,11 @@ private: cNotifyWriteThread m_NotifyWriteThread; - cListenThread m_ListenThreadIPv4; // IPv4 - cListenThread m_ListenThreadIPv6; // IPv6 + cListenThread m_ListenThreadIPv4; + cListenThread m_ListenThreadIPv6; cCriticalSection m_CSClients; // Locks client list - cClientHandleList m_Clients; // Clients that are connected to the server + cClientHandleList m_Clients; // Clients that are connected to the server cSocketThreads m_SocketThreads; @@ -120,12 +119,14 @@ private: unsigned int m_Milliseconds; bool m_bIsConnected; // true - connected false - not connected - int m_iServerPort; bool m_bRestarting; - CryptoPP::RSA::PrivateKey m_PrivateKey; - CryptoPP::RSA::PublicKey m_PublicKey; + CryptoPP::RSA::PrivateKey m_PrivateKey; + CryptoPP::RSA::PublicKey m_PublicKey; + + cRCONServer m_RCONServer; + cServer(void); ~cServer(); @@ -140,9 +141,3 @@ private: - -#endif // CSERVER_H_INCLUDED - - - -