summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--VC2008/MCServer.vcproj8
-rw-r--r--source/OSSupport/ListenThread.cpp25
-rw-r--r--source/OSSupport/ListenThread.h7
-rw-r--r--source/RCONServer.cpp289
-rw-r--r--source/RCONServer.h106
-rw-r--r--source/Server.cpp7
-rw-r--r--source/Server.h23
7 files changed, 436 insertions, 29 deletions
diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj
index 904c0bd3b..d1e1a5e14 100644
--- a/VC2008/MCServer.vcproj
+++ b/VC2008/MCServer.vcproj
@@ -611,6 +611,14 @@
>
</File>
<File
+ RelativePath="..\source\RCONServer.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\source\RCONServer.h"
+ >
+ </File>
+ <File
RelativePath="..\source\ReferenceManager.cpp"
>
</File>
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
-
-
-
-