diff options
author | Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> | 2022-08-03 01:40:26 +0200 |
---|---|---|
committer | Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> | 2022-08-03 01:40:26 +0200 |
commit | 566d3dc33b9c8be39efe429f8ca848f3ff7580ea (patch) | |
tree | 3a16ebc701310d2f8e950514c56f89ce8179dfa5 | |
parent | 671de14bdaf2ee0178aaeba6d069afb51072e4ec (diff) | |
download | NorthstarLauncher-566d3dc33b9c8be39efe429f8ca848f3ff7580ea.tar.gz NorthstarLauncher-566d3dc33b9c8be39efe429f8ca848f3ff7580ea.zip |
RCON system overhaul
* Implemented robust length-prefix framing logic for non-blocking sockets (previously used character sequences to determine length, but you cannot use character sequences on protocol buffers as its binary data. This logic should fix all problems regarding some commands not getting networked properly to the server and stuff not getting printed on the client).
* Increased buffer size to std::vector::max_size when netconsole is authenticated (MAX_NETCONSOLE_INPUT_LEN still remains enforced on accepted but not authenticated connections to prevent attackers from crashing the server).
* Process max 1024 bytes each recv buffer iteration.
* Additional optimizations and cleanup.
-rw-r--r-- | NetConsole/netconsole.cpp | 195 | ||||
-rw-r--r-- | NetConsole/netconsole.h | 24 | ||||
-rw-r--r-- | NorthstarDedicatedTest/cl_rcon.cpp | 142 | ||||
-rw-r--r-- | NorthstarDedicatedTest/cl_rcon.h | 12 | ||||
-rw-r--r-- | NorthstarDedicatedTest/host_state.cpp | 8 | ||||
-rw-r--r-- | NorthstarDedicatedTest/igameserverdata.h | 26 | ||||
-rw-r--r-- | NorthstarDedicatedTest/logging.cpp | 4 | ||||
-rw-r--r-- | NorthstarDedicatedTest/rcon_shared.cpp | 23 | ||||
-rw-r--r-- | NorthstarDedicatedTest/socketcreator.cpp | 15 | ||||
-rw-r--r-- | NorthstarDedicatedTest/squirrel.cpp | 4 | ||||
-rw-r--r-- | NorthstarDedicatedTest/sv_rcon.cpp | 228 | ||||
-rw-r--r-- | NorthstarDedicatedTest/sv_rcon.h | 25 |
12 files changed, 478 insertions, 228 deletions
diff --git a/NetConsole/netconsole.cpp b/NetConsole/netconsole.cpp index 4df47647..7dfec29b 100644 --- a/NetConsole/netconsole.cpp +++ b/NetConsole/netconsole.cpp @@ -13,9 +13,19 @@ #include "netconsole.h" //----------------------------------------------------------------------------- -// Purpose: destructor +// Purpose: //----------------------------------------------------------------------------- -CNetCon::~CNetCon() +CNetCon::CNetCon(void) + : m_bInitialized(false), m_bNoColor(false), m_bQuitApplication(false), m_abPromptConnect(true), m_abConnEstablished(false) +{ + m_pNetAdr2 = new CNetAdr2("localhost", "37015"); + m_pSocket = new CSocketCreator(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNetCon::~CNetCon(void) { delete m_pNetAdr2; delete m_pSocket; @@ -27,12 +37,12 @@ CNetCon::~CNetCon() //----------------------------------------------------------------------------- bool CNetCon::Init(void) { - WSAData wsaData{}; + WSAData wsaData {}; int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); if (nError != 0) { - spdlog::error("Failed to start Winsock via WSAStartup: ({})", NET_ErrorString(WSAGetLastError())); + spdlog::error("Failed to start Winsock via WSAStartup: ({:s})", NET_ErrorString(WSAGetLastError())); return false; } @@ -56,7 +66,7 @@ bool CNetCon::Shutdown(void) int nError = ::WSACleanup(); if (nError != 0) { - spdlog::error("Failed to stop winsock via WSACleanup: ({})", NET_ErrorString(WSAGetLastError())); + spdlog::error("Failed to stop winsock via WSACleanup: ({:s})", NET_ErrorString(WSAGetLastError())); return false; } return true; @@ -71,7 +81,7 @@ void CNetCon::TermSetup(void) HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFOEX sbInfoEx{}; + CONSOLE_SCREEN_BUFFER_INFOEX sbInfoEx {}; COLORREF storedBG = sbInfoEx.ColorTable[0]; sbInfoEx.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); @@ -101,22 +111,23 @@ void CNetCon::UserInput(void) this->Disconnect(); return; } - size_t nPos = svInput.find(" "); - if (!svInput.empty() && nPos > 0 && nPos < svInput.size() && nPos != svInput.size()) - { - std::string svSecondArg = svInput.substr(nPos + 1); - std::string svFirstArg = svInput; - svFirstArg = svFirstArg.erase(svFirstArg.find(" ")); - if (strcmp(svFirstArg.c_str(), "PASS") == 0) // Auth with RCON server. + std::vector<std::string> vSubStrings = StringSplit(svInput, ' ', 2); + if (vSubStrings.size() > 1) + { + if (strcmp(vSubStrings[0].c_str(), "PASS") == 0) // Auth with RCON server. { - std::string svSerialized = this->Serialize(svSecondArg, "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); + std::string svSerialized = this->Serialize(vSubStrings[1], "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); this->Send(svSerialized); } - else if (strcmp(svFirstArg.c_str(), "SET") == 0) // Set value query. + else if (strcmp(vSubStrings[0].c_str(), "SET") == 0) // Set value query. { - std::string svSerialized = this->Serialize(svFirstArg, svSecondArg, cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE); - this->Send(svSerialized); + if (vSubStrings.size() > 2) + { + std::string svSerialized = + this->Serialize(vSubStrings[1], vSubStrings[2], cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE); + this->Send(svSerialized); + } } else // Execute command query. { @@ -124,7 +135,7 @@ void CNetCon::UserInput(void) this->Send(svSerialized); } } - else // Single arg command query. + else if (!svInput.empty()) // Single arg command query. { std::string svSerialized = this->Serialize(svInput.c_str(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND); this->Send(svSerialized); @@ -132,16 +143,19 @@ void CNetCon::UserInput(void) } else // Setup connection from input. { - size_t nPos = svInput.find(" "); - if (!svInput.empty() && nPos > 0 && nPos < svInput.size() && nPos != svInput.size()) + if (!svInput.empty()) { - std::string svInPort = svInput.substr(nPos + 1); - std::string svInAdr = svInput.erase(svInput.find(" ")); - - if (!this->Connect(svInAdr, svInPort)) + std::string::size_type nPos = svInput.find(' '); + if (nPos > 0 && nPos < svInput.size() && nPos != svInput.size()) { - m_abPromptConnect = true; - return; + std::string svInPort = svInput.substr(nPos + 1); + std::string svInAdr = svInput.erase(svInput.find(' ')); + + if (!this->Connect(svInAdr, svInPort)) + { + m_abPromptConnect = true; + return; + } } } else // Initialize as [127.0.0.1]:37015. @@ -180,7 +194,10 @@ void CNetCon::RunFrame(void) // Purpose: checks if application should be terminated // Output : true for termination, false otherwise //----------------------------------------------------------------------------- -bool CNetCon::ShouldQuit(void) const { return this->m_bQuitApplication; } +bool CNetCon::ShouldQuit(void) const +{ + return this->m_bQuitApplication; +} //----------------------------------------------------------------------------- // Purpose: connect to specified address and port @@ -201,7 +218,7 @@ bool CNetCon::Connect(const std::string& svInAdr, const std::string& svInPort) spdlog::warn("Failed to connect. Error: (SOCKET_ERROR). Verify IP and PORT."); return false; } - spdlog::info("Connected to: {}", m_pNetAdr2->GetIPAndPort()); + spdlog::info("Connected to: {:s}", m_pNetAdr2->GetIPAndPort()); m_abConnEstablished = true; return true; @@ -212,7 +229,7 @@ bool CNetCon::Connect(const std::string& svInAdr, const std::string& svInPort) //----------------------------------------------------------------------------- void CNetCon::Disconnect(void) { - ::closesocket(m_pSocket->GetAcceptedSocketHandle(0)); + m_pSocket->CloseAcceptedSocket(0); m_abPromptConnect = true; m_abConnEstablished = false; } @@ -223,7 +240,16 @@ void CNetCon::Disconnect(void) //----------------------------------------------------------------------------- void CNetCon::Send(const std::string& svMessage) const { - int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, svMessage.c_str(), svMessage.size(), MSG_NOSIGNAL); + std::ostringstream ssSendBuf; + + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size())); + ssSendBuf << svMessage; + + int nSendResult = ::send( + m_pSocket->GetAcceptedSocketData(0)->m_hSocket, ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL); if (nSendResult == SOCKET_ERROR) { spdlog::warn("Failed to send message: (SOCKET_ERROR)"); @@ -235,10 +261,11 @@ void CNetCon::Send(const std::string& svMessage) const //----------------------------------------------------------------------------- void CNetCon::Recv(void) { - static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{}; + static char szRecvBuf[1024]; + CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(0); { ////////////////////////////////////////////// - int nPendingLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK); + int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK); if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking()) { return; @@ -252,13 +279,11 @@ void CNetCon::Recv(void) } ////////////////////////////////////////////// u_long nReadLen; // Find out how much we have to read. - ::ioctlsocket(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, FIONREAD, &nReadLen); + ::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen); while (nReadLen > 0) { - memset(szRecvBuf, '\0', sizeof(szRecvBuf)); - int nRecvLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL); - + int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL); if (nRecvLen == 0 && m_abConnEstablished) // Socket was closed. { this->Disconnect(); @@ -267,50 +292,68 @@ void CNetCon::Recv(void) } if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking()) { + spdlog::error("RCON Cmd: recv error ({:s})", NET_ErrorString(WSAGetLastError())); break; } nReadLen -= nRecvLen; // Process what we've got. - this->ProcessBuffer(szRecvBuf, nRecvLen); + this->ProcessBuffer(szRecvBuf, nRecvLen, pData); } } //----------------------------------------------------------------------------- -// Purpose: handles input response buffer -// Input : *pszIn - +// Purpose: parses input response buffer using length-prefix framing +// Input : *pRecvBuf - // nRecvLen - +// *pData - //----------------------------------------------------------------------------- -void CNetCon::ProcessBuffer(const char* pszIn, int nRecvLen) const +void CNetCon::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData) { - int nCharsInRespondBuffer = 0; - char szInputRespondBuffer[MAX_NETCONSOLE_INPUT_LEN]{}; - - while (nRecvLen) + while (nRecvLen > 0) { - switch (*pszIn) + if (pData->m_nPayloadLen) { - case '\r': - { - if (nCharsInRespondBuffer) + if (pData->m_nPayloadRead < pData->m_nPayloadLen) { - sv_rcon::response sv_response = this->Deserialize(szInputRespondBuffer); - this->ProcessMessage(sv_response); + pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf; + + pRecvBuf++; + nRecvLen--; + } + if (pData->m_nPayloadRead == pData->m_nPayloadLen) + { + this->ProcessMessage( + this->Deserialize(std::string(reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen))); + + pData->m_nPayloadLen = 0; + pData->m_nPayloadRead = 0; } - nCharsInRespondBuffer = 0; - break; } + else if (pData->m_nPayloadRead < sizeof(int)) // Read size field. + { + pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf; - default: + pRecvBuf++; + nRecvLen--; + } + else // Build prefix. { - if (nCharsInRespondBuffer < MAX_NETCONSOLE_INPUT_LEN - 1) + pData->m_nPayloadLen = static_cast<int>( + pData->m_RecvBuffer[0] << 24 | pData->m_RecvBuffer[1] << 16 | pData->m_RecvBuffer[2] << 8 | pData->m_RecvBuffer[3]); + pData->m_nPayloadRead = 0; + + if (pData->m_nPayloadLen < 0) { - szInputRespondBuffer[nCharsInRespondBuffer++] = *pszIn; + spdlog::error("RCON Cmd: sync error ({:d})", pData->m_nPayloadLen); + this->Disconnect(); // Out of sync (irrecoverable). + + break; + } + else + { + pData->m_RecvBuffer.resize(pData->m_nPayloadLen); } - break; - } } - pszIn++; - nRecvLen--; } } @@ -327,7 +370,7 @@ void CNetCon::ProcessMessage(const sv_rcon::response& sv_response) const { std::string svOut = sv_response.responsebuf(); svOut.erase(std::remove(svOut.begin(), svOut.end(), '\n'), svOut.end()); - spdlog::info(svOut.c_str()); + spdlog::info(svOut); break; } default: @@ -366,7 +409,7 @@ std::string CNetCon::Serialize(const std::string& svReqBuf, const std::string& s break; } } - return cl_request.SerializeAsString().append("\r"); + return cl_request.SerializeAsString(); } //----------------------------------------------------------------------------- @@ -377,7 +420,7 @@ std::string CNetCon::Serialize(const std::string& svReqBuf, const std::string& s sv_rcon::response CNetCon::Deserialize(const std::string& svBuf) const { sv_rcon::response sv_response; - sv_response.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size())); + sv_response.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size())); return sv_response; } @@ -417,4 +460,32 @@ int main(int argc, char* argv[]) } return ERROR_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// +// For splitting a string into substrings by delimiter. +std::vector<std::string> +StringSplit(std::string svInput, char cDelim, size_t nMax) // Move this to a dedicated string util file if one ever gets created. +{ + std::string svSubString; + std::vector<std::string> vSubStrings; + + svInput = svInput + cDelim; + + for (size_t i = 0; i < svInput.size(); i++) + { + if (i != (svInput.size() - 1) && vSubStrings.size() >= nMax || svInput[i] != cDelim) + { + svSubString += svInput[i]; + } + else + { + if (svSubString.size() != 0) + { + vSubStrings.push_back(svSubString); + } + svSubString.clear(); + } + } + return vSubStrings; }
\ No newline at end of file diff --git a/NetConsole/netconsole.h b/NetConsole/netconsole.h index dbd6eb2c..729c37b0 100644 --- a/NetConsole/netconsole.h +++ b/NetConsole/netconsole.h @@ -12,7 +12,8 @@ constexpr const char* NETCON_VERSION = "2.0.0.1"; class CNetCon { public: - ~CNetCon(); + CNetCon(void); + ~CNetCon(void); bool Init(void); bool Shutdown(void); @@ -29,18 +30,21 @@ class CNetCon void Send(const std::string& svMessage) const; void Recv(void); - void ProcessBuffer(const char* pszIn, int nRecvLen) const; + void ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData); void ProcessMessage(const sv_rcon::response& sv_response) const; std::string Serialize(const std::string& svReqBuf, const std::string& svReqVal, cl_rcon::request_t request_t) const; sv_rcon::response Deserialize(const std::string& svBuf) const; private: - CNetAdr2* m_pNetAdr2 = new CNetAdr2("localhost", "37015"); - CSocketCreator* m_pSocket = new CSocketCreator(); - - bool m_bInitialized = false; - bool m_bQuitApplication = false; - std::atomic<bool> m_abPromptConnect{true}; - std::atomic<bool> m_abConnEstablished{false}; -};
\ No newline at end of file + CNetAdr2* m_pNetAdr2; + CSocketCreator* m_pSocket; + + bool m_bInitialized; + bool m_bNoColor; + bool m_bQuitApplication; + std::atomic<bool> m_abPromptConnect; + std::atomic<bool> m_abConnEstablished; +}; + +std::vector<std::string> StringSplit(std::string svInput, char cDelim, size_t nMax);
\ No newline at end of file diff --git a/NorthstarDedicatedTest/cl_rcon.cpp b/NorthstarDedicatedTest/cl_rcon.cpp index 839f40bd..8278fa03 100644 --- a/NorthstarDedicatedTest/cl_rcon.cpp +++ b/NorthstarDedicatedTest/cl_rcon.cpp @@ -12,12 +12,22 @@ #include "sv_rcon.pb.h" #include "cl_rcon.pb.h" #include "cl_rcon.h" +#include "net.h" #include "igameserverdata.h" //----------------------------------------------------------------------------- -// Purpose: destructor +// Purpose: //----------------------------------------------------------------------------- -CRConClient::~CRConClient() +CRConClient::CRConClient() : m_bInitialized(false), m_bConnEstablished(false) +{ + m_pNetAdr2 = new CNetAdr2("localhost", "37015"); + m_pSocket = new CSocketCreator(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRConClient::~CRConClient(void) { delete m_pNetAdr2; delete m_pSocket; @@ -28,6 +38,10 @@ CRConClient::~CRConClient() //----------------------------------------------------------------------------- void CRConClient::Init(void) { + if (!m_bInitialized) + { + this->SetPassword(CVar_rcon_password->GetString()); + } m_bInitialized = true; } @@ -43,6 +57,24 @@ void CRConClient::Shutdown(void) } //----------------------------------------------------------------------------- +// Purpose: changes the password +// Input : *pszPassword - +// Output : true on success, false otherwise +//----------------------------------------------------------------------------- +bool CRConClient::SetPassword(const char* pszPassword) +{ + if (std::strlen(pszPassword) < 8) + { + if (std::strlen(pszPassword) > 0) + { + spdlog::info("Remote server access requires a password of at least 8 characters"); + } + return false; + } + return true; +} + +//----------------------------------------------------------------------------- // Purpose: client rcon main processing loop //----------------------------------------------------------------------------- void CRConClient::RunFrame(void) @@ -67,10 +99,10 @@ bool CRConClient::Connect(void) if (m_pSocket->ConnectSocket(*m_pNetAdr2, true) == SOCKET_ERROR) { - spdlog::info("Connection to RCON server '{}' failed: (SOCKET_ERROR)", m_pNetAdr2->GetIPAndPort()); + spdlog::info("Connection to RCON server '{:s}' failed: (SOCKET_ERROR)", m_pNetAdr2->GetIPAndPort()); return false; } - spdlog::info("Connected to: {}", m_pNetAdr2->GetIPAndPort().c_str()); + spdlog::info("Connected to: {:s}", m_pNetAdr2->GetIPAndPort()); m_bConnEstablished = true; return true; @@ -92,10 +124,10 @@ bool CRConClient::Connect(const std::string& svInAdr, const std::string& svInPor if (m_pSocket->ConnectSocket(*m_pNetAdr2, true) == SOCKET_ERROR) { - spdlog::info("Connection to RCON server '{}' failed: (SOCKET_ERROR)", m_pNetAdr2->GetIPAndPort()); + spdlog::info("Connection to RCON server '{:s}' failed: (SOCKET_ERROR)", m_pNetAdr2->GetIPAndPort()); return false; } - spdlog::info("Connected to: {}", m_pNetAdr2->GetIPAndPort().c_str()); + spdlog::info("Connected to: {:s}", m_pNetAdr2->GetIPAndPort()); m_bConnEstablished = true; return true; @@ -106,7 +138,7 @@ bool CRConClient::Connect(const std::string& svInAdr, const std::string& svInPor //----------------------------------------------------------------------------- void CRConClient::Disconnect(void) { - ::closesocket(m_pSocket->GetAcceptedSocketHandle(0)); + m_pSocket->CloseAcceptedSocket(0); m_bConnEstablished = false; } @@ -116,7 +148,16 @@ void CRConClient::Disconnect(void) //----------------------------------------------------------------------------- void CRConClient::Send(const std::string& svMessage) const { - int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, svMessage.c_str(), svMessage.size(), MSG_NOSIGNAL); + std::ostringstream ssSendBuf; + + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size())); + ssSendBuf << svMessage; + + int nSendResult = ::send( + m_pSocket->GetAcceptedSocketData(0)->m_hSocket, ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL); if (nSendResult == SOCKET_ERROR) { spdlog::info("Failed to send RCON message: (SOCKET_ERROR)"); @@ -128,10 +169,11 @@ void CRConClient::Send(const std::string& svMessage) const //----------------------------------------------------------------------------- void CRConClient::Recv(void) { - static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN] {}; + static char szRecvBuf[1024]; + CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(0); { ////////////////////////////////////////////// - int nPendingLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK); + int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK); if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking()) { return; @@ -145,13 +187,11 @@ void CRConClient::Recv(void) } ////////////////////////////////////////////// u_long nReadLen; // Find out how much we have to read. - ::ioctlsocket(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, FIONREAD, &nReadLen); + ::ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen); while (nReadLen > 0) { - memset(szRecvBuf, '\0', sizeof(szRecvBuf)); - int nRecvLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL); - + int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL); if (nRecvLen == 0 && m_bConnEstablished) // Socket was closed. { this->Disconnect(); @@ -160,50 +200,68 @@ void CRConClient::Recv(void) } if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking()) { + spdlog::error("RCON Cmd: recv error ({:s})", NET_ErrorString(WSAGetLastError())); break; } nReadLen -= nRecvLen; // Process what we've got. - this->ProcessBuffer(szRecvBuf, nRecvLen); + this->ProcessBuffer(szRecvBuf, nRecvLen, pData); } } //----------------------------------------------------------------------------- -// Purpose: handles input response buffer +// Purpose: parses input response buffer using length-prefix framing // Input : *pszIn - // nRecvLen - +// *pData - //----------------------------------------------------------------------------- -void CRConClient::ProcessBuffer(const char* pszIn, int nRecvLen) const +void CRConClient::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData) { - int nCharsInRespondBuffer = 0; - char szInputRespondBuffer[MAX_NETCONSOLE_INPUT_LEN] {}; - - while (nRecvLen) + while (nRecvLen > 0) { - switch (*pszIn) + if (pData->m_nPayloadLen) { - case '\r': - { - if (nCharsInRespondBuffer) + if (pData->m_nPayloadRead < pData->m_nPayloadLen) { - sv_rcon::response sv_response = this->Deserialize(szInputRespondBuffer); - this->ProcessMessage(sv_response); + pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf; + + pRecvBuf++; + nRecvLen--; + } + if (pData->m_nPayloadRead == pData->m_nPayloadLen) + { + this->ProcessMessage( + this->Deserialize(std::string(reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen))); + + pData->m_nPayloadLen = 0; + pData->m_nPayloadRead = 0; } - nCharsInRespondBuffer = 0; - break; } + else if (pData->m_nPayloadRead < sizeof(int)) // Read size field. + { + pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf; - default: + pRecvBuf++; + nRecvLen--; + } + else // Build prefix. { - if (nCharsInRespondBuffer < MAX_NETCONSOLE_INPUT_LEN - 1) + pData->m_nPayloadLen = static_cast<int>( + pData->m_RecvBuffer[0] << 24 | pData->m_RecvBuffer[1] << 16 | pData->m_RecvBuffer[2] << 8 | pData->m_RecvBuffer[3]); + pData->m_nPayloadRead = 0; + + if (pData->m_nPayloadLen < 0) { - szInputRespondBuffer[nCharsInRespondBuffer++] = *pszIn; + spdlog::error("RCON Cmd: sync error ({:d})", pData->m_nPayloadLen); + this->Disconnect(); // Out of sync (irrecoverable). + + break; + } + else + { + pData->m_RecvBuffer.resize(pData->m_nPayloadLen); } - break; - } } - pszIn++; - nRecvLen--; } } @@ -220,14 +278,14 @@ void CRConClient::ProcessMessage(const sv_rcon::response& sv_response) const case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH: { svOut.erase(std::remove(svOut.begin(), svOut.end(), '\n'), svOut.end()); - spdlog::info("{}", svOut.c_str()); + spdlog::info("{:s}", svOut); break; } case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG: { // !TODO: Network the enum to differentiate script/engine logs. svOut.erase(std::remove(svOut.begin(), svOut.end(), '\n'), svOut.end()); - spdlog::info("{}", svOut.c_str()); + spdlog::info("{:s}", svOut); break; } default: @@ -266,7 +324,7 @@ std::string CRConClient::Serialize(const std::string& svReqBuf, const std::strin break; } } - return cl_request.SerializeAsString().append("\r"); + return cl_request.SerializeAsString(); } //----------------------------------------------------------------------------- @@ -277,7 +335,7 @@ std::string CRConClient::Serialize(const std::string& svReqBuf, const std::strin sv_rcon::response CRConClient::Deserialize(const std::string& svBuf) const { sv_rcon::response sv_response; - sv_response.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size())); + sv_response.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size())); return sv_response; } @@ -301,3 +359,7 @@ bool CRConClient::IsConnected(void) const } /////////////////////////////////////////////////////////////////////////////// CRConClient* g_pRConClient = new CRConClient(); +CRConClient* RCONClient() +{ + return g_pRConClient; +}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/cl_rcon.h b/NorthstarDedicatedTest/cl_rcon.h index 1b9e2dec..aedc579e 100644 --- a/NorthstarDedicatedTest/cl_rcon.h +++ b/NorthstarDedicatedTest/cl_rcon.h @@ -7,12 +7,13 @@ class CRConClient { public: - CRConClient(void) {}; + CRConClient(void); ~CRConClient(void); void Init(void); void Shutdown(void); + bool SetPassword(const char* pszPassword); void RunFrame(void); bool Connect(void); @@ -22,7 +23,7 @@ class CRConClient void Send(const std::string& svMessage) const; void Recv(void); - void ProcessBuffer(const char* pszIn, int nRecvLen) const; + void ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData); void ProcessMessage(const sv_rcon::response& sv_response) const; std::string Serialize(const std::string& svReqBuf, const std::string& svReqVal, cl_rcon::request_t request_t) const; @@ -32,10 +33,11 @@ class CRConClient bool IsConnected(void) const; private: - CNetAdr2* m_pNetAdr2 = new CNetAdr2("localhost", "37015"); - CSocketCreator* m_pSocket = new CSocketCreator(); + CNetAdr2* m_pNetAdr2; + CSocketCreator* m_pSocket; bool m_bInitialized = false; bool m_bConnEstablished = false; }; -extern CRConClient* g_pRConClient;
\ No newline at end of file +extern CRConClient* g_pRConClient; +CRConClient* RCONClient();
\ No newline at end of file diff --git a/NorthstarDedicatedTest/host_state.cpp b/NorthstarDedicatedTest/host_state.cpp index 11138479..39fb931d 100644 --- a/NorthstarDedicatedTest/host_state.cpp +++ b/NorthstarDedicatedTest/host_state.cpp @@ -19,24 +19,24 @@ void CHostState__FrameUpdateHook(CHostState* thisptr) { Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec rcon_server", cmd_source_t::kCommandSrcCode); Cbuf_Execute(); - g_pRConServer->Init(); + RCONServer()->Init(); } else { Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec rcon_client", cmd_source_t::kCommandSrcCode); Cbuf_Execute(); - g_pRConClient->Init(); + RCONClient()->Init(); } bInitialized = true; } if (IsDedicated()) { - g_pRConServer->RunFrame(); + RCONServer()->RunFrame(); } else { - g_pRConClient->RunFrame(); + RCONClient()->RunFrame(); } CHostState__FrameUpdate(thisptr); } diff --git a/NorthstarDedicatedTest/igameserverdata.h b/NorthstarDedicatedTest/igameserverdata.h index 9cc6c759..0cd96080 100644 --- a/NorthstarDedicatedTest/igameserverdata.h +++ b/NorthstarDedicatedTest/igameserverdata.h @@ -30,21 +30,27 @@ enum class ServerDataResponseType_t : int class CConnectedNetConsoleData { public: - SocketHandle_t m_hSocket {}; - int m_nCharsInCommandBuffer {}; - char m_pszInputCommandBuffer[MAX_NETCONSOLE_INPUT_LEN] {}; - bool m_bValidated {}; // Revalidates netconsole if false. - bool m_bAuthorized {}; // Set to true after netconsole successfully authed. - bool m_bInputOnly {}; // If set, don't send spew to this net console. - int m_nFailedAttempts {}; // Num failed authentication attempts. - int m_nIgnoredMessage {}; // Count how many times client ignored the no-auth message. + SocketHandle_t m_hSocket; + int m_nPayloadLen; // Num bytes for this message. + int m_nPayloadRead; // Num read bytes from input buffer. + int m_nFailedAttempts; // Num failed authentication attempts. + int m_nIgnoredMessage; // Count how many times client ignored the no-auth message. + bool m_bValidated; // Revalidates netconsole if false. + bool m_bAuthorized; // Set to true after successfull netconsole auth. + bool m_bInputOnly; // If set, don't send spew to this net console. + std::vector<uint8_t> m_RecvBuffer; CConnectedNetConsoleData(SocketHandle_t hSocket = -1) { - m_nCharsInCommandBuffer = 0; - m_bAuthorized = false; m_hSocket = hSocket; + m_nPayloadLen = 0; + m_nPayloadRead = 0; + m_nFailedAttempts = 0; + m_nIgnoredMessage = 0; + m_bValidated = false; + m_bAuthorized = false; m_bInputOnly = false; + m_RecvBuffer.reserve(sizeof(int)); // Reserve enough for length-prefix. } }; diff --git a/NorthstarDedicatedTest/logging.cpp b/NorthstarDedicatedTest/logging.cpp index 7b9f34c1..c4565d69 100644 --- a/NorthstarDedicatedTest/logging.cpp +++ b/NorthstarDedicatedTest/logging.cpp @@ -380,7 +380,7 @@ void EngineSpewFuncHook(void* engineServer, SpewType_t type, const char* format, { char sendbuf[2048] {}; snprintf(sendbuf, sizeof(sendbuf), "[SERVER %s] %s", typeStr, formatted); - g_pRConServer->Send(sendbuf); + RCONServer()->Send(RCONServer()->Serialize(sendbuf, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG)); } } } @@ -407,7 +407,7 @@ void Status_ConMsg_Hook(const char* text, ...) { if (CVar_sv_rcon_sendlogs->GetBool()) { - g_pRConServer->Send(formatted); + RCONServer()->Send(RCONServer()->Serialize(formatted, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG)); } } } diff --git a/NorthstarDedicatedTest/rcon_shared.cpp b/NorthstarDedicatedTest/rcon_shared.cpp index c4dc4c6c..fc220cb2 100644 --- a/NorthstarDedicatedTest/rcon_shared.cpp +++ b/NorthstarDedicatedTest/rcon_shared.cpp @@ -32,42 +32,41 @@ void _RCON_CmdQuery_f(const CCommand& args) if (args.ArgC() < 2) { - if (g_pRConClient->IsInitialized() && !g_pRConClient->IsConnected()) + if (RCONClient()->IsInitialized() && !RCONClient()->IsConnected()) { - g_pRConClient->Connect(); + RCONClient()->Connect(); } } else { - if (!g_pRConClient->IsInitialized()) + if (!RCONClient()->IsInitialized()) { spdlog::warn("Failed to issue command to RCON server: uninitialized\n"); return; } - else if (g_pRConClient->IsConnected()) + else if (RCONClient()->IsConnected()) { if (strcmp(args.Arg(1), "PASS") == 0) // Auth with RCON server using rcon_password ConVar value. { std::string svCmdQuery; if (args.ArgC() > 2) { - svCmdQuery = g_pRConClient->Serialize(args.Arg(2), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); + svCmdQuery = RCONClient()->Serialize(args.Arg(2), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); } else // Use 'rcon_password' ConVar as password. { - svCmdQuery = g_pRConClient->Serialize(CVar_rcon_password->GetString(), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); + svCmdQuery = RCONClient()->Serialize(CVar_rcon_password->GetString(), "", cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); } - g_pRConClient->Send(svCmdQuery); + RCONClient()->Send(svCmdQuery); return; } else if (strcmp(args.Arg(1), "disconnect") == 0) // Disconnect from RCON server. { - g_pRConClient->Disconnect(); + RCONClient()->Disconnect(); return; } - std::string svCmdQuery = g_pRConClient->Serialize(args.ArgS(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND); - g_pRConClient->Send(svCmdQuery); + RCONClient()->Send(RCONClient()->Serialize(args.ArgS(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND)); return; } else @@ -91,9 +90,9 @@ void _RCON_Disconnect_f(const CCommand& args) { return; } - if (g_pRConClient->IsConnected()) + if (RCONClient()->IsConnected()) { - g_pRConClient->Disconnect(); + RCONClient()->Disconnect(); spdlog::info("User closed RCON connection"); } } diff --git a/NorthstarDedicatedTest/socketcreator.cpp b/NorthstarDedicatedTest/socketcreator.cpp index 03b5b76f..d500eace 100644 --- a/NorthstarDedicatedTest/socketcreator.cpp +++ b/NorthstarDedicatedTest/socketcreator.cpp @@ -254,7 +254,7 @@ int CSocketCreator::OnSocketAccepted(SocketHandle_t hSocket, CNetAdr2 netAdr2) m_hAcceptedSockets.push_back(pNewEntry); - int nIndex = (int)m_hAcceptedSockets.size() - 1; + int nIndex = static_cast<int>(m_hAcceptedSockets.size()) - 1; return nIndex; } @@ -271,6 +271,8 @@ void CSocketCreator::CloseAcceptedSocket(int nIndex) AcceptedSocket_t& connected = m_hAcceptedSockets[nIndex]; ::closesocket(connected.m_hSocket); + delete connected.m_pData; + m_hAcceptedSockets.erase(m_hAcceptedSockets.begin() + nIndex); } @@ -284,12 +286,15 @@ void CSocketCreator::CloseAllAcceptedSockets(void) { AcceptedSocket_t& connected = m_hAcceptedSockets[i]; ::closesocket(connected.m_hSocket); + + delete connected.m_pData; } m_hAcceptedSockets.clear(); } //----------------------------------------------------------------------------- // Purpose: returns true if the listening socket is created and listening +// Output : bool //----------------------------------------------------------------------------- bool CSocketCreator::IsListening(void) const { @@ -298,6 +303,7 @@ bool CSocketCreator::IsListening(void) const //----------------------------------------------------------------------------- // Purpose: returns true if the socket would block because of the last socket command +// Output : bool //----------------------------------------------------------------------------- bool CSocketCreator::IsSocketBlocking(void) const { @@ -306,6 +312,7 @@ bool CSocketCreator::IsSocketBlocking(void) const //----------------------------------------------------------------------------- // Purpose: returns accepted socket count +// Output : int //----------------------------------------------------------------------------- int CSocketCreator::GetAcceptedSocketCount(void) const { @@ -314,6 +321,8 @@ int CSocketCreator::GetAcceptedSocketCount(void) const //----------------------------------------------------------------------------- // Purpose: returns accepted socket handle +// Input : nIndex - +// Output : SocketHandle_t //----------------------------------------------------------------------------- SocketHandle_t CSocketCreator::GetAcceptedSocketHandle(int nIndex) const { @@ -322,6 +331,8 @@ SocketHandle_t CSocketCreator::GetAcceptedSocketHandle(int nIndex) const //----------------------------------------------------------------------------- // Purpose: returns accepted socket address +// Input : nIndex - +// Output : const CNetAdr2& //----------------------------------------------------------------------------- const CNetAdr2& CSocketCreator::GetAcceptedSocketAddress(int nIndex) const { @@ -330,6 +341,8 @@ const CNetAdr2& CSocketCreator::GetAcceptedSocketAddress(int nIndex) const //----------------------------------------------------------------------------- // Purpose: returns accepted socket data +// Input : nIndex - +// Output : CConnectedNetConsoleData* //----------------------------------------------------------------------------- CConnectedNetConsoleData* CSocketCreator::GetAcceptedSocketData(int nIndex) const { diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp index cba0d880..4ff7ee9d 100644 --- a/NorthstarDedicatedTest/squirrel.cpp +++ b/NorthstarDedicatedTest/squirrel.cpp @@ -276,7 +276,7 @@ template <ScriptContext context> SQInteger SQPrintHook(void* sqvm, char* fmt, .. char sendbuf[1024] {}; snprintf(sendbuf, sizeof(sendbuf), "[%s SCRIPT] %s", GetContextName(context), buf); - g_pRConServer->Send(sendbuf); + RCONServer()->Send(RCONServer()->Serialize(sendbuf, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG)); } } } @@ -355,7 +355,7 @@ template <ScriptContext context> void ScriptCompileErrorHook(void* sqvm, const c { if (CVar_sv_rcon_sendlogs->GetBool()) { - g_pRConServer->Send(buffer); + RCONServer()->Send(RCONServer()->Serialize(buffer, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG)); } } diff --git a/NorthstarDedicatedTest/sv_rcon.cpp b/NorthstarDedicatedTest/sv_rcon.cpp index 527e5f5e..ef855af5 100644 --- a/NorthstarDedicatedTest/sv_rcon.cpp +++ b/NorthstarDedicatedTest/sv_rcon.cpp @@ -8,6 +8,7 @@ #include "concommand.h" #include "cvar.h" #include "convar.h" +#include "net.h" #include "NetAdr2.h" #include "socketcreator.h" #include "rcon_shared.h" @@ -19,34 +20,39 @@ #include "igameserverdata.h" //----------------------------------------------------------------------------- -// Purpose: destructor +// Purpose: //----------------------------------------------------------------------------- -CRConServer::~CRConServer() +CRConServer::CRConServer(void) : m_bInitialized(false), m_nConnIndex(0) { - delete m_pNetAdr2; - delete m_pSocket; + m_pAdr2 = new CNetAdr2(); + m_pSocket = new CSocketCreator(); } //----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRConServer::~CRConServer(void) +{ + delete m_pAdr2; + delete m_pSocket; +} +//----------------------------------------------------------------------------- // Purpose: NETCON systems init //----------------------------------------------------------------------------- void CRConServer::Init(void) { - if (std::strlen(CVar_rcon_password->GetString()) < 8) + if (!m_bInitialized) { - if (std::strlen(CVar_rcon_password->GetString()) > 0) + if (!this->SetPassword(CVar_rcon_password->GetString())) { - spdlog::info("Remote server access requires a password of at least 8 characters"); + return; } - this->Shutdown(); - return; } static ConVar* hostport = g_pCVar->FindVar("hostport"); - m_pNetAdr2->SetIPAndPort(CVar_rcon_address->GetString(), hostport->GetString()); - m_pSocket->CreateListenSocket(*m_pNetAdr2, false); - m_svPasswordHash = sha256(CVar_rcon_password->GetString()); + m_pAdr2->SetIPAndPort(CVar_rcon_address->GetString(), hostport->GetString()); + m_pSocket->CreateListenSocket(*m_pAdr2, false); spdlog::info("Remote server access initialized"); m_bInitialized = true; @@ -93,9 +99,35 @@ void CRConServer::Think(void) { if (!m_pSocket->IsListening()) { - m_pSocket->CreateListenSocket(*m_pNetAdr2, false); + m_pSocket->CreateListenSocket(*m_pAdr2, false); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: changes the password +// Input : *pszPassword - +// Output : true on success, false otherwise +//----------------------------------------------------------------------------- +bool CRConServer::SetPassword(const char* pszPassword) +{ + m_bInitialized = false; + m_pSocket->CloseAllAcceptedSockets(); + + if (std::strlen(pszPassword) < 8) + { + if (std::strlen(pszPassword) > 0) + { + spdlog::warn("Remote server access requires a password of at least 8 characters"); } + this->Shutdown(); + return false; } + m_svPasswordHash = sha256(pszPassword); + spdlog::info("Password hash ('{:s}')", m_svPasswordHash); + + m_bInitialized = true; + return true; } //----------------------------------------------------------------------------- @@ -112,32 +144,57 @@ void CRConServer::RunFrame(void) } //----------------------------------------------------------------------------- -// Purpose: send message +// Purpose: send message to all connected sockets // Input : *svMessage - //----------------------------------------------------------------------------- void CRConServer::Send(const std::string& svMessage) const { - int nCount = m_pSocket->GetAcceptedSocketCount(); - - for (int i = nCount - 1; i >= 0; i--) + if (int nCount = m_pSocket->GetAcceptedSocketCount()) { - CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i); + std::ostringstream ssSendBuf; - if (pData->m_bAuthorized) + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size())); + ssSendBuf << svMessage; + + for (int i = nCount - 1; i >= 0; i--) { - std::string svFinal = this->Serialize(svMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG); - ::send(pData->m_hSocket, svFinal.c_str(), static_cast<int>(svFinal.size()), MSG_NOSIGNAL); + CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i); + if (pData->m_bAuthorized) + { + ::send(pData->m_hSocket, ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL); + } } } } //----------------------------------------------------------------------------- +// Purpose: send message to specific connected socket +// Input : hSocket - +// *svMessage - +//----------------------------------------------------------------------------- +void CRConServer::Send(SocketHandle_t hSocket, const std::string& svMessage) const +{ + std::ostringstream ssSendBuf; + + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 24); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 16); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size()) >> 8); + ssSendBuf << static_cast<uint8_t>(static_cast<int>(svMessage.size())); + ssSendBuf << svMessage; + + ::send(hSocket, ssSendBuf.str().data(), static_cast<int>(ssSendBuf.str().size()), MSG_NOSIGNAL); +} + +//----------------------------------------------------------------------------- // Purpose: receive message //----------------------------------------------------------------------------- void CRConServer::Recv(void) { int nCount = m_pSocket->GetAcceptedSocketCount(); - static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN] {}; + static char szRecvBuf[1024]; for (m_nConnIndex = nCount - 1; m_nConnIndex >= 0; m_nConnIndex--) { @@ -145,13 +202,12 @@ void CRConServer::Recv(void) { ////////////////////////////////////////////// if (this->CheckForBan(pData)) { - std::string svNoAuth = this->Serialize(s_pszBannedMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH); - ::send(pData->m_hSocket, svNoAuth.c_str(), static_cast<int>(svNoAuth.size()), MSG_NOSIGNAL); + this->Send(pData->m_hSocket, this->Serialize(s_pszBannedMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH)); this->CloseConnection(); continue; } - int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK); + int nPendingLen = ::recv(pData->m_hSocket, szRecvBuf, sizeof(char), MSG_PEEK); if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking()) { continue; @@ -168,9 +224,7 @@ void CRConServer::Recv(void) while (nReadLen > 0) { - memset(szRecvBuf, '\0', sizeof(szRecvBuf)); int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL); - if (nRecvLen == 0) // Socket was closed. { this->CloseConnection(); @@ -178,6 +232,7 @@ void CRConServer::Recv(void) } if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking()) { + spdlog::error("RCON Cmd: recv error ({:s})", NET_ErrorString(WSAGetLastError())); break; } @@ -219,7 +274,7 @@ std::string CRConServer::Serialize(const std::string& svRspBuf, const std::strin break; } } - return sv_response.SerializeAsString().append("\r"); + return sv_response.SerializeAsString(); } //----------------------------------------------------------------------------- @@ -230,7 +285,7 @@ std::string CRConServer::Serialize(const std::string& svRspBuf, const std::strin cl_rcon::request CRConServer::Deserialize(const std::string& svBuf) const { cl_rcon::request cl_request; - cl_request.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size())); + cl_request.ParseFromArray(svBuf.data(), static_cast<int>(svBuf.size())); return cl_request; } @@ -239,9 +294,6 @@ cl_rcon::request CRConServer::Deserialize(const std::string& svBuf) const // Purpose: authenticate new connections // Input : *cl_request - // *pData - -// Todo : implement logic for key exchange instead so we never network our -// password in plain text over the wire. create a cvar for this so user could -// also opt out and use legacy authentication instead for older RCON clients //----------------------------------------------------------------------------- void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNetConsoleData* pData) { @@ -249,27 +301,25 @@ void CRConServer::Authenticate(const cl_rcon::request& cl_request, CConnectedNet { return; } - else + else // Authorize. { if (this->Comparator(cl_request.requestbuf())) { pData->m_bAuthorized = true; m_pSocket->CloseListenSocket(); - this->CloseNonAuthConnection(); - std::string svAuth = this->Serialize(s_pszAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH); - ::send(pData->m_hSocket, svAuth.c_str(), static_cast<int>(svAuth.size()), MSG_NOSIGNAL); + this->CloseNonAuthConnection(); + this->Send(pData->m_hSocket, this->Serialize(s_pszAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH)); } else // Bad password. { CNetAdr2 netAdr2 = m_pSocket->GetAcceptedSocketAddress(m_nConnIndex); if (CVar_sv_rcon_debug->GetBool()) { - spdlog::info("Bad RCON password attempt from '{}'", netAdr2.GetIPAndPort().c_str()); + spdlog::info("Bad RCON password attempt from '{:s}'", netAdr2.GetIPAndPort()); } - std::string svWrongPass = this->Serialize(s_pszWrongPwMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH); - ::send(pData->m_hSocket, svWrongPass.c_str(), static_cast<int>(svWrongPass.size()), MSG_NOSIGNAL); + this->Send(pData->m_hSocket, this->Serialize(s_pszWrongPwMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH)); pData->m_bAuthorized = false; pData->m_bValidated = false; @@ -289,8 +339,8 @@ bool CRConServer::Comparator(std::string svPassword) const if (CVar_sv_rcon_debug->GetBool()) { spdlog::info("+---------------------------------------------------------------------------+"); - spdlog::info("] Server: '{}'[", m_svPasswordHash.c_str()); - spdlog::info("] Client: '{}'[", svPassword.c_str()); + spdlog::info("] Server: '{:s}'[", m_svPasswordHash); + spdlog::info("] Client: '{:s}'[", svPassword); spdlog::info("+---------------------------------------------------------------------------+"); } if (memcmp(svPassword.c_str(), m_svPasswordHash.c_str(), SHA256::DIGEST_SIZE) == 0) @@ -301,38 +351,67 @@ bool CRConServer::Comparator(std::string svPassword) const } //----------------------------------------------------------------------------- -// Purpose: handles input command buffer -// Input : *pszIn - +// Purpose: parses input response buffer using length-prefix framing +// Input : *pRecvBuf - // nRecvLen - // *pData - //----------------------------------------------------------------------------- -void CRConServer::ProcessBuffer(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData) +void CRConServer::ProcessBuffer(const char* pRecvBuf, int nRecvLen, CConnectedNetConsoleData* pData) { - while (nRecvLen) + while (nRecvLen > 0) { - switch (*pszIn) + if (pData->m_nPayloadLen) { - case '\r': - { - if (pData->m_nCharsInCommandBuffer) + if (pData->m_nPayloadRead < pData->m_nPayloadLen) { - cl_rcon::request cl_request = this->Deserialize(pData->m_pszInputCommandBuffer); - this->ProcessMessage(cl_request); + pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf; + + pRecvBuf++; + nRecvLen--; } - pData->m_nCharsInCommandBuffer = 0; - break; + if (pData->m_nPayloadRead == pData->m_nPayloadLen) + { + this->ProcessMessage( + this->Deserialize(std::string(reinterpret_cast<char*>(pData->m_RecvBuffer.data()), pData->m_nPayloadLen))); + + pData->m_nPayloadLen = 0; + pData->m_nPayloadRead = 0; + } + } + else if (pData->m_nPayloadRead < sizeof(int)) // Read size field. + { + pData->m_RecvBuffer[pData->m_nPayloadRead++] = *pRecvBuf; + + pRecvBuf++; + nRecvLen--; } - default: + else // Build prefix. { - if (pData->m_nCharsInCommandBuffer < MAX_NETCONSOLE_INPUT_LEN - 1) + pData->m_nPayloadLen = static_cast<int>( + pData->m_RecvBuffer[0] << 24 | pData->m_RecvBuffer[1] << 16 | pData->m_RecvBuffer[2] << 8 | pData->m_RecvBuffer[3]); + pData->m_nPayloadRead = 0; + + if (!pData->m_bAuthorized) + { + if (pData->m_nPayloadLen > MAX_NETCONSOLE_INPUT_LEN) + { + this->CloseConnection(); // Sending large messages while not authenticated. + break; + } + } + + if (pData->m_nPayloadLen < 0) { - pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer++] = *pszIn; + spdlog::error("RCON Cmd: sync error ({:d})", pData->m_nPayloadLen); + this->CloseConnection(); // Out of sync (irrecoverable). + + break; + } + else + { + pData->m_RecvBuffer.resize(pData->m_nPayloadLen); } - break; - } } - pszIn++; - nRecvLen--; } } @@ -347,8 +426,7 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request) if (!pData->m_bAuthorized && cl_request.requesttype() != cl_rcon::request_t::SERVERDATA_REQUEST_AUTH) { // Notify net console that authentication is required. - std::string svMessage = this->Serialize(s_pszNoAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH); - ::send(pData->m_hSocket, svMessage.c_str(), static_cast<int>(svMessage.size()), MSG_NOSIGNAL); + this->Send(pData->m_hSocket, this->Serialize(s_pszNoAuthMessage, "", sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH)); pData->m_bValidated = false; pData->m_nIgnoredMessage++; @@ -362,12 +440,18 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request) break; } case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND: + { + if (pData->m_bAuthorized) // Only execute if auth was succesfull. + { + this->Execute(cl_request, false); + } + break; + } case cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE: { - // Only execute if auth was succesfull. if (pData->m_bAuthorized) { - this->Execute(cl_request); + this->Execute(cl_request, true); } break; } @@ -389,15 +473,16 @@ void CRConServer::ProcessMessage(const cl_rcon::request& cl_request) //----------------------------------------------------------------------------- // Purpose: execute commands issued from net console // Input : *cl_request - +// bConVar - //----------------------------------------------------------------------------- -void CRConServer::Execute(const cl_rcon::request& cl_request) const +void CRConServer::Execute(const cl_rcon::request& cl_request, bool bConVar) const { ConVar* pConVar = g_pCVar->FindVar(cl_request.requestbuf().c_str()); - if (pConVar) + if (pConVar) // Set value without running the callback. { pConVar->SetValue(cl_request.requestval().c_str()); } - else // Execute command with "<val>". + else if (!bConVar) // Execute command with "<val>". { Cbuf_AddText(Cbuf_GetCurrentPlayer(), cl_request.requestbuf().c_str(), cmd_source_t::kCommandSrcCode); Cbuf_Execute(); @@ -435,7 +520,7 @@ bool CRConServer::CheckForBan(CConnectedNetConsoleData* pData) return false; } - spdlog::info("Banned '{}' for RCON hacking attempts", netAdr2.GetIPAndPort().c_str()); + spdlog::info("Banned '{:s}' for RCON hacking attempts", netAdr2.GetIPAndPort()); m_vBannedAddress.push_back(netAdr2.GetIP(true)); return true; } @@ -452,7 +537,7 @@ void CRConServer::CloseConnection(void) // NETMGR { // Inform server owner when authenticated connection has been closed. CNetAdr2 netAdr2 = m_pSocket->GetAcceptedSocketAddress(m_nConnIndex); - spdlog::info("Net console '{}' closed RCON connection", netAdr2.GetIPAndPort().c_str()); + spdlog::info("Net console '{:s}' closed RCON connection", netAdr2.GetIPAndPort()); } m_pSocket->CloseAcceptedSocket(m_nConnIndex); } @@ -463,7 +548,6 @@ void CRConServer::CloseConnection(void) // NETMGR void CRConServer::CloseNonAuthConnection(void) { int nCount = m_pSocket->GetAcceptedSocketCount(); - for (int i = nCount - 1; i >= 0; i--) { CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i); @@ -476,3 +560,7 @@ void CRConServer::CloseNonAuthConnection(void) } /////////////////////////////////////////////////////////////////////////////// CRConServer* g_pRConServer = new CRConServer(); +CRConServer* RCONServer() +{ + return g_pRConServer; +} diff --git a/NorthstarDedicatedTest/sv_rcon.h b/NorthstarDedicatedTest/sv_rcon.h index ec762d25..8d89c615 100644 --- a/NorthstarDedicatedTest/sv_rcon.h +++ b/NorthstarDedicatedTest/sv_rcon.h @@ -5,23 +5,27 @@ #include "cl_rcon.pb.h" #include "igameserverdata.h" -constexpr char s_pszNoAuthMessage[] = "This server is password protected for console access. Must send 'PASS <password>' command.\n\r"; -constexpr char s_pszWrongPwMessage[] = "Password incorrect.\n\r"; -constexpr char s_pszBannedMessage[] = "Go away.\n\r"; -constexpr char s_pszAuthMessage[] = "RCON authentication succesfull.\n\r"; +constexpr char s_pszNoAuthMessage[] = + "This server is password protected for console access. Authenticate with 'PASS <password>' command.\n"; +constexpr char s_pszWrongPwMessage[] = "Admin password incorrect.\n"; +constexpr char s_pszBannedMessage[] = "Go away.\n"; +constexpr char s_pszAuthMessage[] = "RCON authentication successfull.\n"; class CRConServer { public: + CRConServer(); ~CRConServer(); void Init(void); void Shutdown(void); + bool SetPassword(const char* pszPassword); void Think(void); void RunFrame(void); void Send(const std::string& svMessage) const; + void Send(SocketHandle_t hSocket, const std::string& svMessage) const; void Recv(void); std::string Serialize(const std::string& svRspBuf, const std::string& svRspVal, sv_rcon::response_t response_t) const; @@ -33,18 +37,19 @@ class CRConServer void ProcessBuffer(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData); void ProcessMessage(const cl_rcon::request& cl_request); - void Execute(const cl_rcon::request& cl_request) const; + void Execute(const cl_rcon::request& cl_request, bool bConVar) const; bool CheckForBan(CConnectedNetConsoleData* pData); void CloseConnection(void); void CloseNonAuthConnection(void); private: - bool m_bInitialized = false; - int m_nConnIndex = 0; - CNetAdr2* m_pNetAdr2 = new CNetAdr2(); - CSocketCreator* m_pSocket = new CSocketCreator(); + bool m_bInitialized; + int m_nConnIndex; + CNetAdr2* m_pAdr2; + CSocketCreator* m_pSocket; std::vector<std::string> m_vBannedAddress; std::string m_svPasswordHash; }; -extern CRConServer* g_pRConServer;
\ No newline at end of file +extern CRConServer* g_pRConServer; +CRConServer* RCONServer();
\ No newline at end of file |