Skip to content

Commit

Permalink
fix: rcon now properly cares for authentication.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaskowicz1 committed Dec 17, 2023
1 parent 31f9ddd commit 110dc59
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 58 deletions.
22 changes: 14 additions & 8 deletions include/rcon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(const std::string& retrieved_data)> callback;
std::function<void(const rcon_response& response)> callback;

rcon_queued_request(const std::string& _data, const int32_t _id, const data_type _type, std::function<void(const std::string& retrieved_data)> _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<void(const rcon_response& retrieved_data)> _callback) : data(_data), id(_id), type(_type), callback(_callback) {}
};

class rcon {
Expand Down Expand Up @@ -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<void(const std::string& retrieved_data)> callback = {});
void send_data(const std::string& data, const int32_t id, data_type type, std::function<void(const rcon_response& retrieved_data)> callback = {});

/**
* @brief Send data to the connected RCON server.
Expand All @@ -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:

Expand Down Expand Up @@ -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`)
Expand All @@ -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<size_t>(buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24);
inline const int byte32_to_int(unsigned char* buffer) {
return static_cast<int>(buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24);
}
};
34 changes: 17 additions & 17 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -117,12 +117,12 @@ int main() {

auto command_to_run = std::get<std::string>(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));
});
}
});
Expand All @@ -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(), ')', ' ');
Expand All @@ -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(), ')', ' ');
Expand Down
88 changes: 55 additions & 33 deletions src/rcon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<void(const std::string& retrieved_data)> callback) {
void rcon::send_data(const std::string& data, const int32_t id, data_type type, std::function<void(const rcon_response& retrieved_data)> 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;
Expand All @@ -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() {
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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};
Expand All @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit 110dc59

Please sign in to comment.