From 110dc595ce16e9fefd0b5d73654f15fdf871b60b Mon Sep 17 00:00:00 2001 From: Archie Jaskowicz Date: Sun, 17 Dec 2023 22:05:48 +0000 Subject: [PATCH] fix: rcon now properly cares for authentication. --- include/rcon.h | 22 ++++++++----- src/main.cpp | 34 +++++++++---------- src/rcon.cpp | 88 +++++++++++++++++++++++++++++++------------------- 3 files changed, 86 insertions(+), 58 deletions(-) diff --git a/include/rcon.h b/include/rcon.h index ef158a1..a8a93a0 100644 --- a/include/rcon.h +++ b/include/rcon.h @@ -27,19 +27,25 @@ enum data_type { struct rcon_packet { unsigned int bytes; unsigned char* data; + bool server_responded; ~rcon_packet() { delete[] data; } }; +struct rcon_response { + std::string data; + bool server_responded; +}; + struct rcon_queued_request { std::string data; int32_t id; data_type type; - std::function callback; + std::function callback; - rcon_queued_request(const std::string& _data, const int32_t _id, const data_type _type, std::function _callback) : data(_data), id(_id), type(_type), callback(_callback) {} + rcon_queued_request(const std::string& _data, const int32_t _id, const data_type _type, std::function _callback) : data(_data), id(_id), type(_type), callback(_callback) {} }; class rcon { @@ -70,7 +76,7 @@ class rcon { * * @warning If you are expecting no response from the server, do NOT use the callback. You will halt the RCON process until the next received message (which will chain). */ - void send_data(const std::string& data, const int32_t id, data_type type, std::function callback = {}); + void send_data(const std::string& data, const int32_t id, data_type type, std::function callback = {}); /** * @brief Send data to the connected RCON server. @@ -80,11 +86,11 @@ class rcon { * @param type The type of packet to send. * @param feedback Should the client expect a message back from the server? (optional, default is true). * - * @warning If you are expecting no response from the server, set `feedback` to false. Otherwise, you will halt the RCON process until the next received message (which will chain). + * @warning If you are expecting no response from the server, set `feedback` to false. Otherwise, you will halt the RCON process for 4 seconds. * * @returns Data given by the server from the request. */ - const std::string send_data_sync(const std::string data, const int32_t id, data_type type, bool feedback = true); + const rcon_response send_data_sync(const std::string data, const int32_t id, data_type type, bool feedback = true); private: @@ -114,7 +120,7 @@ class rcon { * * @return Data given by the server. */ - std::string receive_information(int32_t id); + rcon_response receive_information(int32_t id, data_type type); /** * @brief Gathers all the packet's content (based on the length returned by `read_packet_length`) @@ -123,7 +129,7 @@ class rcon { const size_t read_packet_length(); - inline const size_t byte32_to_int(unsigned char* buffer) { - return static_cast(buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24); + inline const int byte32_to_int(unsigned char* buffer) { + return static_cast(buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24); } }; diff --git a/src/main.cpp b/src/main.cpp index 84832e5..1fefb95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,24 +85,24 @@ int main() { bot.on_slashcommand([&rcon_client](const dpp::slashcommand_t& event) { if (event.command.get_command_name() == "evolution") { - rcon_client.send_data("/evolution", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const std::string& data) { - event.reply(dpp::message(data).set_flags(dpp::m_ephemeral)); + rcon_client.send_data("/evolution", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const rcon_response& response) { + event.reply(dpp::message(response.data).set_flags(dpp::m_ephemeral)); }); } else if (event.command.get_command_name() == "time") { - rcon_client.send_data("/time", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const std::string& data) { - event.reply(dpp::message("Server uptime: " + data).set_flags(dpp::m_ephemeral)); + rcon_client.send_data("/time", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const rcon_response& response) { + event.reply(dpp::message("Server uptime: " + response.data).set_flags(dpp::m_ephemeral)); }); } else if (event.command.get_command_name() == "version") { - rcon_client.send_data("/version", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const std::string& data) { - event.reply(dpp::message("Factorio version: " + data).set_flags(dpp::m_ephemeral)); + rcon_client.send_data("/version", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const rcon_response& response) { + event.reply(dpp::message("Factorio version: " + response.data).set_flags(dpp::m_ephemeral)); }); } else if (event.command.get_command_name() == "players") { - rcon_client.send_data("/players online", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const std::string& data) { - event.reply(dpp::message(data).set_flags(dpp::m_ephemeral)); + rcon_client.send_data("/players online", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const rcon_response& response) { + event.reply(dpp::message(response.data).set_flags(dpp::m_ephemeral)); }); } else if (event.command.get_command_name() == "seed") { - rcon_client.send_data("/seed", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const std::string& data) { - event.reply(dpp::message(data).set_flags(dpp::m_ephemeral)); + rcon_client.send_data("/seed", 3, data_type::SERVERDATA_EXECCOMMAND, [event](const rcon_response& response) { + event.reply(dpp::message(response.data).set_flags(dpp::m_ephemeral)); }); } else if (event.command.get_command_name() == "command") { if(FDR::config.allow_achievements) { @@ -117,12 +117,12 @@ int main() { auto command_to_run = std::get(event.get_parameter("cmd")); - rcon_client.send_data("/command " + command_to_run, 3, data_type::SERVERDATA_EXECCOMMAND, [event](const std::string& data) { - if(data.empty()) { + rcon_client.send_data("/command " + command_to_run, 3, data_type::SERVERDATA_EXECCOMMAND, [event](const rcon_response& response) { + if(response.data.empty()) { return; } - event.reply(dpp::message(data).set_flags(dpp::m_ephemeral)); + event.reply(dpp::message(response.data).set_flags(dpp::m_ephemeral)); }); } }); @@ -144,8 +144,8 @@ int main() { } rcon_client.send_data("/players online count", 2, data_type::SERVERDATA_EXECCOMMAND, - [&bot](const std::string& data) { - std::string players = data; + [&bot](const rcon_response& response) { + std::string players = response.data; std::replace(players.begin(), players.end(), ':', ' '); std::replace(players.begin(), players.end(), '(', ' '); std::replace(players.begin(), players.end(), ')', ' '); @@ -154,8 +154,8 @@ int main() { /* Create a timer that runs every 120 seconds, that sets the status */ bot.start_timer([&bot, &rcon_client](const dpp::timer& timer) { - rcon_client.send_data("/players online count", 2, data_type::SERVERDATA_EXECCOMMAND, [&bot](const std::string& data) { - std::string players = data; + rcon_client.send_data("/players online count", 2, data_type::SERVERDATA_EXECCOMMAND, [&bot](const rcon_response& response) { + std::string players = response.data; std::replace(players.begin(), players.end(), ':', ' '); std::replace(players.begin(), players.end(), '(', ' '); std::replace(players.begin(), players.end(), ')', ' '); diff --git a/src/rcon.cpp b/src/rcon.cpp index 6fd7223..33dd6a2 100644 --- a/src/rcon.cpp +++ b/src/rcon.cpp @@ -19,12 +19,20 @@ rcon::rcon(const std::string& addr, const unsigned int _port, const std::string& return; } - connected = true; - std::cout << "Connected successfully! Sending login data..." << "\n"; - // The server will send SERVERDATA_AUTH_RESPONSE once it's happy. - send_data_sync(pass, 1, data_type::SERVERDATA_AUTH); + // The server will send SERVERDATA_AUTH_RESPONSE once it's happy. If it's not -1, the server will have accepted us! + rcon_response response = send_data_sync(pass, 1, data_type::SERVERDATA_AUTH, true); + + if(!response.server_responded) { + std::cout << "Login data was incorrect. RCON will now abort." << "\n"; + close(sock); + return; + } + + std::cout << "Sent login data." << "\n"; + + connected = true; std::thread queue_runner([this]() { while(connected) { @@ -47,14 +55,14 @@ rcon::rcon(const std::string& addr, const unsigned int _port, const std::string& queue_runner.detach(); }; -void rcon::send_data(const std::string& data, const int32_t id, data_type type, std::function callback) { +void rcon::send_data(const std::string& data, const int32_t id, data_type type, std::function callback) { requests_queued.emplace_back(data, id, type, callback); } -const std::string rcon::send_data_sync(const std::string data, const int32_t id, data_type type, bool feedback) { - if(!connected) { +const rcon_response rcon::send_data_sync(const std::string data, const int32_t id, data_type type, bool feedback) { + if(!connected && type != data_type::SERVERDATA_AUTH) { std::cout << "Cannot send data when not connected." << "\n"; - return ""; + return {"", false}; } unsigned long long packet_len = data.length() + HEADER_SIZE; @@ -63,15 +71,16 @@ const std::string rcon::send_data_sync(const std::string data, const int32_t id, if(::send(sock, packet, packet_len, 0) < 0) { std::cout << "Sending failed!" << "\n"; - return ""; + return {"", false}; } - if(type != SERVERDATA_EXECCOMMAND || !feedback) { - return ""; + if(!feedback) { + // Because we do not want any feedback, we just send no data and say the server didn't respond. + return {"", false}; } // Server will send a SERVERDATA_RESPONSE_VALUE packet. - return receive_information(id); + return receive_information(id, type); } bool rcon::connect() { @@ -94,24 +103,22 @@ bool rcon::connect() { // Set a timeout of 4 seconds. struct timeval tv{}; - tv.tv_sec = 4; + tv.tv_sec = DEFAULT_TIMEOUT; tv.tv_usec = 0; fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - - // Create temp status - int status = -1; // Connect to the socket and set the status to our temp status. - if((status = ::connect(sock, (struct sockaddr *)&server, sizeof(server))) == -1) { + if(::connect(sock, (struct sockaddr *)&server, sizeof(server)) == -1) { if(errno != EINPROGRESS) { return false; } } - status = select(sock +1, nullptr, &fds, nullptr, &tv); + // Create temp status + int status = select(sock +1, nullptr, &fds, nullptr, &tv); fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) & ~O_NONBLOCK); // If status wasn't zero, we successfully connected. @@ -142,18 +149,31 @@ void rcon::form_packet(unsigned char packet[], const std::string& data, int32_t } } -std::string rcon::receive_information(int32_t id) { +rcon_response rcon::receive_information(int32_t id, data_type type) { // Whilst this loop is better than a while loop, // it should really just keep going for a certain amount of seconds. for(int i=0; i < 500; i++) { rcon_packet packet = read_packet(); if(packet.bytes == 0) { - return ""; + if(type != SERVERDATA_AUTH) + return {"", packet.server_responded}; + else + continue; } unsigned char* buffer = packet.data; + if(type == SERVERDATA_AUTH) { + if(byte32_to_int(buffer) == -1) { + return {"", false}; + } else { + if(byte32_to_int(packet.data) == id) { + return {"", true}; + } + } + } + int offset = packet.bytes - HEADER_SIZE + 3; if(offset == -1) @@ -162,17 +182,23 @@ std::string rcon::receive_information(int32_t id) { std::string part(&buffer[8], &buffer[8] + offset); if(byte32_to_int(packet.data) == id) { - return part; + return {part, packet.server_responded}; } } - return ""; + return {"", false}; } rcon_packet rcon::read_packet() { size_t packet_length = read_packet_length(); - if(packet_length == 0) { - return {0, nullptr}; + /* + * If the packet length is -1, the server didn't respond. + * If the packet length is 0, the server did respond but said nothing. + */ + if(packet_length == -1) { + return {0, nullptr, false}; + } else if(packet_length == 0) { + return {0, nullptr, true}; } auto* buffer = new unsigned char[packet_length]{0}; @@ -181,10 +207,8 @@ rcon_packet rcon::read_packet() { do { size_t recv_bytes = ::recv(sock, buffer, packet_length - bytes, 0); if(recv_bytes == -1) { - if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) { - std::cout << "Did not receive a packet in time. Did the server send a response?"; - return {0, nullptr}; - } + std::cout << "Did not receive a packet in time. Did the server send a response?" << "\n"; + return {0, nullptr, false}; } bytes += recv_bytes; @@ -195,13 +219,11 @@ rcon_packet rcon::read_packet() { } const size_t rcon::read_packet_length() { - unsigned char* buffer = new unsigned char[4]{0}; + auto* buffer = new unsigned char[4]{0}; size_t recv_bytes = ::recv(sock, buffer, 4, 0); if(recv_bytes == -1) { - if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) { - std::cout << "Did not receive a packet in time. Did the server send a response?"; - return 0; - } + std::cout << "Did not receive a packet in time. Did the server send a response?" << "\n"; + return -1; } const size_t len = byte32_to_int(buffer); delete[] buffer;