7 Commits

Author SHA1 Message Date
dba07aacf7 Update CHANGELOG.md 2024-12-06 12:35:30 +02:00
bd76b897de Fix missing newlines in Minecraft RCON messages:
Minecraft servers have a longstanding bug that omits newlines
when sending messages via RCON. This patch manually inserts
newlines to address the issue.

Reference: https://bugs.mojang.com/browse/MC-7569

Fixes #1
2024-12-06 11:00:18 +02:00
1106f27700 Bump version to 0.8.0 2024-12-06 08:47:45 +02:00
00fc3b5bcb Set select() timeout to 5 seconds and replace putchar() loop with fputs() call 2024-12-06 08:47:45 +02:00
a0fe9e1645 Enable input/output buffering and fflush only explicitly
As suggested in old pull request: #39
2024-12-06 08:47:45 +02:00
5f460e8912 Remove Valve protocol checks and set select() timeout to 1.5 seconds
Use the same method for handling multipacket responses across all servers.
2024-12-06 08:47:45 +02:00
6fed74ba74 Implement select() loop to receive all incoming packets:
Send a "multipacket guard" - an empty packet with an invalid 'cmd' field
and a unique packet ID to trigger a reply from the server once the previous
command's reply has been fully sent.

Valve returns an empty payload, while Minecraft includes an error message in the payload.

This workaround ensures that all packets related to the last valid command
are received from the server, avoiding the need to wait for the select() timeout.
2024-12-06 08:47:45 +02:00
3 changed files with 91 additions and 46 deletions

View File

@ -1,17 +1,20 @@
#### Version history: #### Version history:
###### 0.7.3 ###### 0.8.0
- Implement support for multipacket responses
- Add support to Valve style rcon authentication - Add support to Valve style rcon authentication
- Change maximum packet size to correct value (4096 -> 4106) - Change maximum packet size to correct value (4096 -> 4106)
- Attempt to add missing newlines in Minecraft servers
* Currently implemented only for the 'help' command
- Print auth failed message to stderr instead of stdout - Print auth failed message to stderr instead of stdout
- Fail immediately if received packet size is out of spec - Fail immediately if received packet size is out of spec
- Return proper exit code from run_terminal_mode() - Return proper exit code from run_terminal_mode()
- Add error messages to rcon_command() function - Add error messages to rcon_command() function
###### 0.7.2 ###### 0.7.2
- Quit gracefully when Ctrl-D or Ctrl+C is pressed - Quit gracefully when Ctrl-D or Ctrl+C is pressed
- Remove "exit" and "quit" as quitting commands - Remove "exit" and "quit" as quitting commands
* these are actual rcon commands on some servers * These are actual rcon commands on some servers
- Suppress compiler warning (strncpy) - Suppress compiler warning (strncpy)
- Fix erroneous string length in packet building function - Fix erroneous string length in packet building function
- Fix typo in ANSI escape sequence for LCYAN - Fix typo in ANSI escape sequence for LCYAN

View File

@ -1,7 +1,7 @@
.\" Process this file with .\" Process this file with
.\" groff -man -Tascii mcrcon.1 .\" groff -man -Tascii mcrcon.1
.\" .\"
.TH MCRCON 1 "November 2024" "Version 0.7.3" .TH MCRCON 1 "December 2024" "Version 0.8.0"
.SH NAME .SH NAME
mcrcon \- send rcon commands to a Minecraft server mcrcon \- send rcon commands to a Minecraft server
.SH SYNOPSIS .SH SYNOPSIS

126
mcrcon.c
View File

@ -41,7 +41,7 @@
#include <netdb.h> #include <netdb.h>
#endif #endif
#define VERSION "0.7.3" #define VERSION "0.8.0"
#define IN_NAME "mcrcon" #define IN_NAME "mcrcon"
#define VER_STR IN_NAME" "VERSION" (built: "__DATE__" "__TIME__")" #define VER_STR IN_NAME" "VERSION" (built: "__DATE__" "__TIME__")"
@ -95,16 +95,17 @@ void packet_print(rc_packet *packet);
bool rcon_auth(int sock, char *passwd); bool rcon_auth(int sock, char *passwd);
int rcon_command(int sock, char *command); int rcon_command(int sock, char *command);
// ============================================= // =============================================
// GLOBAL VARIABLES // GLOBAL VARIABLES
// ============================================= // =============================================
static int flag_raw_output = 0; static int flag_raw_output = 0;
static int flag_silent_mode = 0; static int flag_silent_mode = 0;
static int flag_disable_colors = 0; static int flag_disable_colors = 0;
static int flag_wait_seconds = 0; static int flag_wait_seconds = 0;
static int global_connection_alive = 1; static int global_connection_alive = 1;
static int global_rsock; static bool global_valve_protocol = false;
static bool global_minecraft_newline_fix = false;
static int global_rsock;
#ifdef _WIN32 #ifdef _WIN32
// console coloring on windows // console coloring on windows
@ -166,10 +167,6 @@ int main(int argc, char *argv[])
if (!port) port = "25575"; if (!port) port = "25575";
if (!host) host = "localhost"; if (!host) host = "localhost";
// disable output buffering (https://github.com/Tiiffi/mcrcon/pull/39)
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if(argc < 1 && pass == NULL) usage(); if(argc < 1 && pass == NULL) usage();
// default getopt error handler enabled // default getopt error handler enabled
@ -483,9 +480,7 @@ void packet_print(rc_packet *packet)
uint8_t *data = packet->data; uint8_t *data = packet->data;
if (flag_raw_output == 1) { if (flag_raw_output == 1) {
for (int i = 0; data[i] != 0; ++i) { fputs((char *) data, stdout);
putchar(data[i]);
}
return; return;
} }
@ -499,34 +494,38 @@ void packet_print(rc_packet *packet)
} else default_color = 0x37; } else default_color = 0x37;
#endif #endif
// colors enabled so try to handle the bukkit colors for terminal bool slash = false;
if (flag_disable_colors == 0) { bool colors_detected = false;
for (i = 0; data[i] != 0; ++i) {
if (data[i] == 0x0A) print_color(default_color); for (i = 0; data[i] != 0; ++i)
else if(data[i] == 0xc2 && data[i + 1] == 0xa7) { {
i += 2; if (data[i] == 0x0A) {
print_color(default_color);
}
else if(data[i] == 0xc2 && data[i + 1] == 0xa7) {
// Disable new line fixes if Bukkit colors are detected
colors_detected = true;
i += 2;
if (flag_disable_colors == 0) {
print_color(data[i]); print_color(data[i]);
continue;
} }
putchar(data[i]); continue;
} }
print_color(default_color); // cancel coloring
} if (colors_detected == false && global_minecraft_newline_fix && data[i] == '/') {
// strip colors slash ? putchar('\n') : (slash = true);
else {
for (i = 0; data[i] != 0; ++i) {
if (data[i] == 0xc2 && data[i + 1] == 0xa7) {
i += 2;
continue;
}
putchar(data[i]);
} }
putchar(data[i]);
} }
print_color(default_color); // cancel coloring
// print newline if string has no newline // print newline if string has no newline
if (data[i - 1] != 10 && data[i - 1] != 13) { if (data[i - 1] != '\n') {
putchar('\n'); putchar('\n');
} }
fflush(stdout);
} }
rc_packet *packet_build(int id, int cmd, char s[static 1]) rc_packet *packet_build(int id, int cmd, char s[static 1])
@ -573,6 +572,7 @@ receive:
* so we have to check packet type and try again if necessary. * so we have to check packet type and try again if necessary.
*/ */
if (packet->cmd != RCON_AUTH_RESPONSE) { if (packet->cmd != RCON_AUTH_RESPONSE) {
global_valve_protocol = true;
goto receive; goto receive;
} }
@ -580,9 +580,11 @@ receive:
return packet->id == -1 ? false : true; return packet->id == -1 ? false : true;
} }
// TODO: Add proper error handling and reporting!
int rcon_command(int sock, char *command) int rcon_command(int sock, char *command)
{ {
if (global_valve_protocol == false && strcasecmp(command, "help") == 0)
global_minecraft_newline_fix = true;
rc_packet *packet = packet_build(RCON_PID, RCON_EXEC_COMMAND, command); rc_packet *packet = packet_build(RCON_PID, RCON_EXEC_COMMAND, command);
if (packet == NULL) { if (packet == NULL) {
log_error("Error: packet build() failed!\n"); log_error("Error: packet build() failed!\n");
@ -594,21 +596,60 @@ int rcon_command(int sock, char *command)
return 0; return 0;
} }
packet = net_recv_packet(sock); // Workaround to handle valve multipacket responses
// This one does not require using select()
packet = packet_build(0xBADA55, 0xBADA55, "");
if (packet == NULL) { if (packet == NULL) {
log_error("Error: net_recv_packet() failed!\n"); log_error("Error: packet build() failed!\n");
return 0; return 0;
} }
if (packet->id != RCON_PID) { if (!net_send_packet(sock, packet)) {
log_error("Error: invalid packet id!\n"); log_error("Error: net_send_packet() failed!\n");
return 0; return 0;
} }
if (!flag_silent_mode) { // initialize stuff for select()
if (packet->size > 10) fd_set read_fds;
packet_print(packet); FD_ZERO(&read_fds);
FD_SET(sock, &read_fds);
// Set 5 second timeout
struct timeval timeout = {0};
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int incoming = 0;
do {
packet = net_recv_packet(sock);
if (packet == NULL) {
log_error("Error: net_recv_packet() failed!\n");
return 0;
}
// Check for packet id and multipacket guard id
if (packet->id != RCON_PID && packet->id != 0xBADA55) {
log_error("Error: invalid packet id!\n");
return 0;
}
if (packet->id == 0xBADA55) break;
if (flag_silent_mode == false) {
if (packet->size > 10)
packet_print(packet);
}
int result = select(sock + 1, &read_fds, NULL, NULL, &timeout);
if (result == -1) {
log_error("Error: select() failed!\n");
return 0;
}
incoming = (result > 0 && FD_ISSET(sock, &read_fds));
} }
while(incoming);
return 1; return 1;
} }
@ -645,6 +686,7 @@ int run_terminal_mode(int sock)
while (global_connection_alive) { while (global_connection_alive) {
putchar('>'); putchar('>');
fflush(stdout);
int len = get_line(command, MAX_COMMAND_LENGTH); int len = get_line(command, MAX_COMMAND_LENGTH);
if (len < 1) continue; if (len < 1) continue;