diff options
-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 |