25 Commits

Author SHA1 Message Date
5d1c87b26f Don't call memcpy() if there is nothing to copy 2024-12-04 08:17:56 +02:00
fd77ad7fc0 packet_build(): add [static 1] to generate warning if NULL pointer is passed 2024-12-03 05:40:45 +02:00
bc5617407d Update CHANGELOG.md 2024-12-02 10:40:51 +02:00
5ae06f4d37 Update man page 2024-12-02 10:35:00 +02:00
233031cdcb Update README.md 2024-12-02 10:31:38 +02:00
ebf5172cd0 Update BUILDING.md 2024-12-02 10:13:38 +02:00
0b72e4d17d Update README.md 2024-12-02 09:56:06 +02:00
af70f87bc1 Update README.md 2024-12-02 09:52:30 +02:00
53fd703911 Rename INSTALL.md to BUILDING.md 2024-12-02 09:42:44 +02:00
c0d28bcb17 Rename INSTALL.md to BUILDING.md 2024-12-02 09:42:11 +02:00
edf8344983 Add "-Wno-gnu-zero-variadic-macro-arguments" flag to suppress Clang warnings about GNU macro extensions 2024-12-02 09:37:08 +02:00
a502204e26 Remove unused headers 2024-12-02 09:34:48 +02:00
7162bc6fe6 - add log_error macro
- rename "flag_connection_alive" variable back to "global_connection_alive"
- return exit code from run_terminal_mode()
- rcon_command(): add error messages
2024-12-02 08:55:18 +02:00
f270a485b5 Remove url from version string 2024-12-02 07:40:34 +02:00
df66816bee Change prefix of flag variables from 'global' to 'flag' 2024-12-02 07:37:12 +02:00
5c7ab407d7 Patch of various fixes, cleanups and unused code removals:
- add MAX_COMMAND_LENGTH to define maximum command length
 - print auth failed message to stderr instead of stdout
 - remove unused net_send() function
 - remove unused net_clean_incoming() function
 - rewrite net_send_packet() function
 - net_recv_packet(): change the type of variable "ret" from int to ssize_t
 - net_recv_packet(): fail immediately if the packet size is out of spec
 - packet_print(): rename variable "def_color" to "default_color"
 - packet_print(): remove unecessary casts
 - packet_build(): use MAX_COMMAND_LENGTH
 - packet_build(): be more explicit in calculation of packet.size
 - packet_build(): use memcpy() instead of strncpy()
 - cast second argument of send()/recv() calls to (char *) so Windows is happy
 - rcon_auth(): change the return type from int to bool
 - run_terminal_mode(): use MAX_COMMAND_LENGTH
2024-12-02 07:32:07 +02:00
c83d96cc91 Add "$(EXENAME).exe" to clean rule so Windows executables are also cleaned up 2024-12-02 06:14:34 +02:00
427fd206ca Oops, uncomment struct field 2024-11-11 23:49:18 +02:00
1a4010cbba Change MAX_PACKET_SIZE and DATA_BUFFSIZE, add notes about packet structure 2024-11-11 22:22:45 +02:00
4488127350 Use fixed width integer types in rcon packet structure 2024-11-10 16:25:19 +02:00
6b563df23d Update README.md 2024-11-09 20:53:55 +02:00
5a2dcf41ac Update README.md 2024-11-09 20:51:36 +02:00
7d3f3c1d61 Update README.md 2024-11-09 20:46:53 +02:00
fc040ce5ea Change maximum packet size to correct value (4096 -> 4106) 2024-11-09 15:22:42 +02:00
489306d4a2 Add windows batch scripts 2024-11-09 13:48:23 +02:00
10 changed files with 286 additions and 222 deletions

29
BUILDING.md Normal file
View File

@ -0,0 +1,29 @@
Building and installing
-----------------------
#### Requirements & dependencies:
- GCC compatible compiler
- POSIX getopt support from **<unistd.h>**
---
#### Compiling
cc -std=gnu99 -Wpedantic -Wall -Wextra -Wno-gnu-zero-variadic-macro-arguments -O2 -o mcrcon mcrcon.c
>[!NOTE]
>If you are compiling on Windows remember to link with winsock by adding `-lws2_32` to your compiler command line.
---
Or you can run **make**
make - compiles mcrcon
make install - installs compiled binaries and manpage to the system
make uninstall - removes binaries and manpage from the system
file install locations:
/usr/local/bin/mcrcon
/usr/local/share/man/man1/mcrcon.1
Makefile **install** and **uninstall** rules are not available on Windows.

View File

@ -2,13 +2,18 @@
###### 0.7.3 ###### 0.7.3
- Add support to Valve style rcon authentication - Add support to Valve style rcon authentication
- Change maximum packet size to correct value (4096 -> 4106)
- Print auth failed message to stderr instead of stdout
- Fail immediately if received packet size is out of spec
- Return proper exit code from run_terminal_mode()
- 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
- Make stdout and stderr unbuffered - Make stdout and stderr unbuffered

View File

@ -1,22 +0,0 @@
Building and installing
-----------------------
Only dependency is C library and POSIX getopt support.
Compiling with GCC or CLANG:
cc -std=gnu99 -Wpedantic -Wall -Wextra -O2 -o mcrcon mcrcon.c
Note: on Windows remember to link with winsock by adding `-lws2_32` to your compiler command line.
Or you can just run "**make**":
make - compiles mcrcon
make install - installs compiled binaries and manpage to the system
make uninstall - removes binaries and manpage from the system
file install locations:
/usr/local/bin/mcrcon
/usr/local/share/man/man1/mcrcon.1
Makefile "**install**" and "**uninstall**" rules are disabled on windows.

View File

@ -1,4 +1,4 @@
Copyright (c) 2012-2021, Tiiffi <tiiffi at gmail> Copyright (c) 2012-2024, Tiiffi <tiiffi at gmail>
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -13,8 +13,8 @@ INSTALL = install
LINKER = LINKER =
RM = rm -v -f RM = rm -v -f
CC = gcc CC ?= gcc
CFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -O2 CFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -Wno-gnu-zero-variadic-macro-arguments -O2
EXTRAFLAGS ?= -fstack-protector-all EXTRAFLAGS ?= -fstack-protector-all
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
@ -44,4 +44,4 @@ endif
.PHONY: clean .PHONY: clean
clean: clean:
$(RM) $(EXENAME) $(RM) $(EXENAME) $(EXENAME).exe

View File

@ -1,12 +1,19 @@
# mcrcon # mcrcon
mcrcon is console based Minecraft [rcon](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) client for remote administration and server maintenance scripts. mcrcon is a command-line [rcon](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) client intended for remote server administration and maintenance automation.
Though originally developed for Minecraft servers, it also works with a variety of other servers using the Valve or Minecraft-style rcon protocol.
--- ---
### Installing: ### Installing
##### via package manager: ##### Binary releases
Pre-built binaries are provided for Linux and Windows: https://github.com/Tiiffi/mcrcon/releases/latest
##### Via package manager
See https://pkgs.org/download/mcrcon and https://repology.org/project/mcrcon/packages for available packages in various Linux distros (note that some packages might be outdated). See https://pkgs.org/download/mcrcon and https://repology.org/project/mcrcon/packages for available packages in various Linux distros (note that some packages might be outdated).
- Fedora: https://packages.fedoraproject.org/pkgs/mcrcon/mcrcon/ - Fedora: https://packages.fedoraproject.org/pkgs/mcrcon/mcrcon/
@ -16,7 +23,7 @@ See https://pkgs.org/download/mcrcon and https://repology.org/project/mcrcon/pac
- Snapcraft: https://snapcraft.io/mcrcon-nsg - Snapcraft: https://snapcraft.io/mcrcon-nsg
- Scoop: https://scoop.sh/#/apps?q=mcrcon - Scoop: https://scoop.sh/#/apps?q=mcrcon
##### building from sources: ##### Building from sources
```sh ```sh
git clone https://github.com/Tiiffi/mcrcon.git git clone https://github.com/Tiiffi/mcrcon.git
cd mcrcon cd mcrcon
@ -25,15 +32,11 @@ make
# install is optional # install is optional
sudo make install sudo make install
``` ```
Check [INSTALL.md](INSTALL.md) for more details. _Check [BUILDING.md](BUILDING.md) for more details._
Precompiled binaries*: https://github.com/Tiiffi/mcrcon/releases/latest
<sub>*Binaries are provided for Linux and Windows.</sub>
--- ---
### Usage: ### Usage
mcrcon [OPTIONS] [COMMANDS] mcrcon [OPTIONS] [COMMANDS]
Sends rcon commands to Minecraft server. Sends rcon commands to Minecraft server.
@ -62,33 +65,44 @@ MCRCON_PASS
- Command-line options will override environment variables - Command-line options will override environment variables
- Rcon commands with spaces must be enclosed in quotes - Rcon commands with spaces must be enclosed in quotes
Example: ###### Example:
> Send three commands ("say", "save-all", "stop") and wait five seconds between the commands. Send three commands ("say", "save-all", "stop") and wait five seconds between the commands:
```mcrcon -H my.minecraft.server -p password -w 5 "say Server is restarting!" save-all stop``` ```sh
mcrcon -H my.minecraft.server -p password -w 5 "say Server is restarting!" save-all stop
```
---
> [!TIP]
>Enable RCON on Minecraft server by adding following lines to [```server.properties```](https://minecraft.gamepedia.com/Server.properties) configuration file.
>```
>enable-rcon=true
>rcon.port=25575
>rcon.password=your_rcon_pasword
>```
--- ---
##### Enable rcon on server ### Contact
Remember to enable rcon by adding following lines to [```server.properties```](https://minecraft.gamepedia.com/Server.properties) file.
```
enable-rcon=true
rcon.port=25575
rcon.password=your_rcon_pasword
```
---
##### Contact:
* WWW: https://github.com/Tiiffi/mcrcon/ * WWW: https://github.com/Tiiffi/mcrcon/
* MAIL: tiiffi+mcrcon at gmail * MAIL: tiiffi+mcrcon at gmail
* BUG REPORTS: https://github.com/Tiiffi/mcrcon/issues/ * ISSUES: https://github.com/Tiiffi/mcrcon/issues/
> [!TIP]
>When reporting issues, please provide the following information:
>
>- Version of mcrcon: Please specify the precise version number
>- Game: Indicate the specific game server you're using (e.g., Minecraft, Valve Source Engine game, ARK, ...)
>- Server version: Provide the exact version of the game server
>- Mods and Extensions: List all mods and extensions used, including their versions
>- Issue Description: Clearly describe the problem you're encountering and the expected behavior.
>- Steps to reproduce
>
>If you're tech-savvy, consider providing a packet capture file (PCAP). Remember to use a fake password.
--- ---
### License ### License
This project is licensed under the zlib License - see the [LICENSE](LICENSE) file for details. This project is licensed under the zlib License - see the [LICENSE](LICENSE) file for details.
---

31
create_shortcut.bat Normal file
View File

@ -0,0 +1,31 @@
@echo off
@cls
@set /p host="Enter host (default: "127.0.0.1"): "
@if "%host%"=="" set host=127.0.0.1
@set /p port="Enter port (default: 25575): "
@if "%port%"=="" set port=25575
@set /p passwd="Enter password: "
@if "%passwd%"=="" set passwd=
set name=connect_%host%-%port%
@set /p name="Enter shortcut name (default: "%name%.bat"): "
@if "%name%"=="" set name=connect_%host%-%port%
set command=@mcrcon.exe -t -H %host% -P %port% -p %passwd%
@echo %command% >> %name%.bat
@echo.
@echo Command: "%command%"
@echo.
@echo Shortcut "%name%.bat" created!
@echo.
@set "host="
@set "port="
@set "passwd="
@pause

29
launch.bat Normal file
View File

@ -0,0 +1,29 @@
@echo off
@cls
@if not exist mcrcon.exe (
@echo ERROR: Cannot find "mcrcon.exe". Bailing out!
@echo.
@pause
@exit
)
@set /p host="Enter host (default: 127.0.0.1): "
@if "%host%"=="" set host=127.0.0.1
@set /p port="Enter port (default: 25575): "
@if "%port%"=="" set port=25575
@set /p passwd="Enter password: "
@if "%passwd%"=="" set passwd=
@echo.
mcrcon.exe -t -H %host% -P %port% -p %passwd%
@echo.
@set "host="
@set "port="
@set "passwd="
@pause

View File

@ -11,7 +11,7 @@ options
commands commands
.B ] .B ]
.SH DESCRIPTION .SH DESCRIPTION
mcrcon is Minecraft rcon client for remote administration and server maintenance scripts. mcrcon is a command-line rcon client intended for remote server administration and maintenance automation.
.SH OPTIONS .SH OPTIONS
.IP -H .IP -H
Server address (default: localhost) Server address (default: localhost)

312
mcrcon.c
View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2012-2021, Tiiffi <tiiffi at gmail> * Copyright (c) 2012-2024, Tiiffi <tiiffi at gmail>
* *
* This software is provided 'as-is', without any express or implied * This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages * warranty. In no event will the authors be held liable for any damages
@ -24,26 +24,20 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <signal.h> #include <signal.h>
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <limits.h>
#ifdef _WIN32 #ifdef _WIN32
// for name resolving on windows #include <winsock2.h>
// enable this if you get compiler whine about getaddrinfo() on windows
//#define _WIN32_WINNT 0x0501
#include <ws2tcpip.h>
#include <winsock2.h>
#include <windows.h> #include <windows.h>
#include <ws2tcpip.h>
#else #else
#include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h> #include <netdb.h>
#endif #endif
@ -57,18 +51,22 @@
#define RCON_AUTH_RESPONSE 2 #define RCON_AUTH_RESPONSE 2
#define RCON_PID 0xBADC0DE #define RCON_PID 0xBADC0DE
#define DATA_BUFFSIZE 4096 #define MAX_COMMAND_LENGTH 4096
#define DATA_BUFFSIZE MAX_COMMAND_LENGTH + 2 // plus two null terminators
#define MAX_PACKET_SIZE 4106 // id (4) + cmd (4) + DATA_BUFFSIZE
#define MIN_PACKET_SIZE 10 // id (4) + cmd (4) + two empty strings (2)
#define log_error(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__);
// rcon packet structure // rcon packet structure
typedef struct _rc_packet { typedef struct {
int size; int32_t size;
int id; int32_t id;
int cmd; int32_t cmd;
char data[DATA_BUFFSIZE]; uint8_t data[DATA_BUFFSIZE];
// ignoring string2 for now // ignoring string2 for now
} rc_packet; } rc_packet;
// =================================== // ===================================
// FUNCTION DEFINITIONS // FUNCTION DEFINITIONS
// =================================== // ===================================
@ -79,10 +77,8 @@ void net_init_WSA(void);
#endif #endif
void net_close(int sd); void net_close(int sd);
int net_connect(const char *host, const char *port); int net_connect(const char *host, const char *port);
int net_send(int sd, const uint8_t *buffer, size_t size); bool net_send_packet(int sd, rc_packet *packet);
int net_send_packet(int sd, rc_packet *packet);
rc_packet* net_recv_packet(int sd); rc_packet* net_recv_packet(int sd);
int net_clean_incoming(int sd, int size);
// Misc stuff // Misc stuff
void usage(void); void usage(void);
@ -94,21 +90,21 @@ int run_terminal_mode(int sock);
int run_commands(int argc, char *argv[]); int run_commands(int argc, char *argv[]);
// Rcon protocol related functions // Rcon protocol related functions
rc_packet* packet_build(int id, int cmd, char *s1); rc_packet* packet_build(int id, int cmd, char s[static 1]);
void packet_print(rc_packet *packet); void packet_print(rc_packet *packet);
int 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 global_raw_output = 0; static int flag_raw_output = 0;
static int global_silent_mode = 0; static int flag_silent_mode = 0;
static int global_disable_colors = 0; static int flag_disable_colors = 0;
static int flag_wait_seconds = 0;
static int global_connection_alive = 1; static int global_connection_alive = 1;
static int global_rsock; static int global_rsock;
static int global_wait_seconds = 0;
#ifdef _WIN32 #ifdef _WIN32
// console coloring on windows // console coloring on windows
@ -122,7 +118,7 @@ void exit_proc(void)
net_close(global_rsock); net_close(global_rsock);
} }
// Check windows & linux behaviour !!! // TODO: check exact windows and linux behaviour
void sighandler(int sig) void sighandler(int sig)
{ {
if (sig == SIGINT) if (sig == SIGINT)
@ -142,17 +138,17 @@ unsigned int mcrcon_parse_seconds(char *str)
long result = strtol(str, &end, 10); long result = strtol(str, &end, 10);
if (errno != 0) { if (errno != 0) {
fprintf(stderr, "-w invalid value.\nerror %d: %s\n", errno, strerror(errno)); log_error("-w invalid value.\nerror %d: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (end == str) { if (end == str) {
fprintf(stderr, "-w invalid value (not a number?)\n"); log_error("-w invalid value (not a number?)\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (result <= 0 || result > MAX_WAIT_TIME) { if (result <= 0 || result > MAX_WAIT_TIME) {
fprintf(stderr, "-w value out of range.\nAcceptable value is 1 - %d (seconds).\n", MAX_WAIT_TIME); log_error("-w value out of range.\nAcceptable value is 1 - %d (seconds).\n", MAX_WAIT_TIME);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -182,20 +178,20 @@ int main(int argc, char *argv[])
while ((opt = getopt(argc, argv, "vrtcshw:H:p:P:")) != -1) while ((opt = getopt(argc, argv, "vrtcshw:H:p:P:")) != -1)
{ {
switch (opt) { switch (opt) {
case 'H': host = optarg; break; case 'H': host = optarg; break;
case 'P': port = optarg; break; case 'P': port = optarg; break;
case 'p': pass = optarg; break; case 'p': pass = optarg; break;
case 'c': global_disable_colors = 1; break; case 'c': flag_disable_colors = 1; break;
case 's': global_silent_mode = 1; break; case 's': flag_silent_mode = 1; break;
case 'i': /* reserved for interp mode */break; case 'i': /* reserved for interp mode */ break;
case 't': terminal_mode = 1; break; case 't': terminal_mode = 1; break;
case 'r': global_raw_output = 1; break; case 'r': flag_raw_output = 1; break;
case 'w': case 'w':
global_wait_seconds = mcrcon_parse_seconds(optarg); flag_wait_seconds = mcrcon_parse_seconds(optarg);
break; break;
case 'v': case 'v':
puts(VER_STR" - https://github.com/Tiiffi/mcrcon"); puts(VER_STR);
puts("Bug reports:\n\ttiiffi+mcrcon at gmail\n\thttps://github.com/Tiiffi/mcrcon/issues/"); puts("Bug reports:\n\ttiiffi+mcrcon at gmail\n\thttps://github.com/Tiiffi/mcrcon/issues/");
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -212,8 +208,9 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if(optind == argc && terminal_mode == 0) if(optind == argc && terminal_mode == 0) {
terminal_mode = 1; terminal_mode = 1;
}
// safety features to prevent "IO: Connection reset" bug on the server side // safety features to prevent "IO: Connection reset" bug on the server side
atexit(&exit_proc); atexit(&exit_proc);
@ -235,13 +232,11 @@ int main(int argc, char *argv[])
// auth & commands // auth & commands
if (rcon_auth(global_rsock, pass)) { if (rcon_auth(global_rsock, pass)) {
if (terminal_mode) if (terminal_mode) exit_code = run_terminal_mode(global_rsock);
run_terminal_mode(global_rsock); else exit_code = run_commands(argc, argv);
else
exit_code = run_commands(argc, argv);
} }
else { // auth failed else { // auth failed
fprintf(stdout, "Authentication failed!\n"); log_error("Authentication failed!\n");
exit_code = EXIT_FAILURE; exit_code = EXIT_FAILURE;
} }
@ -296,7 +291,7 @@ void net_init_WSA(void)
int err = WSAStartup(version, &wsadata); int err = WSAStartup(version, &wsadata);
if (err != 0) { if (err != 0) {
fprintf(stderr, "WSAStartup failed. Error: %d.\n", err); log_error("WSAStartup failed. Error: %d.\n", err);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
@ -313,7 +308,6 @@ void net_close(int sd)
#endif #endif
} }
// Opens and connects socket
// http://man7.org/linux/man-pages/man3/getaddrinfo.3.html // http://man7.org/linux/man-pages/man3/getaddrinfo.3.html
// https://bugs.chromium.org/p/chromium/issues/detail?id=44489 // https://bugs.chromium.org/p/chromium/issues/detail?id=44489
int net_connect(const char *host, const char *port) int net_connect(const char *host, const char *port)
@ -334,11 +328,11 @@ int net_connect(const char *host, const char *port)
int ret = getaddrinfo(host, port, &hints, &server_info); int ret = getaddrinfo(host, port, &hints, &server_info);
if (ret != 0) { if (ret != 0) {
fprintf(stderr, "Name resolution failed.\n"); log_error("Name resolution failed.\n");
#ifdef _WIN32 #ifdef _WIN32
fprintf(stderr, "Error %d: %s", ret, gai_strerror(ret)); log_error("Error %d: %s", ret, gai_strerror(ret));
#else #else
fprintf(stderr, "Error %d: %s\n", ret, gai_strerror(ret)); log_error("Error %d: %s\n", ret, gai_strerror(ret));
#endif #endif
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -362,9 +356,9 @@ int net_connect(const char *host, const char *port)
if (p == NULL) { if (p == NULL) {
/* TODO (Tiiffi): Check why windows does not report errors */ /* TODO (Tiiffi): Check why windows does not report errors */
fprintf(stderr, "Connection failed.\n"); log_error("Connection failed.\n");
#ifndef _WIN32 #ifndef _WIN32
fprintf(stderr, "Error %d: %s\n", errno, strerror(errno)); log_error("Error %d: %s\n", errno, strerror(errno));
#endif #endif
freeaddrinfo(server_info); freeaddrinfo(server_info);
@ -375,81 +369,59 @@ int net_connect(const char *host, const char *port)
return sd; return sd;
} }
int net_send(int sd, const uint8_t *buff, size_t size) bool net_send_packet(int sd, rc_packet *packet)
{ {
size_t sent = 0; size_t sent = 0;
size_t size = packet->size + sizeof(int32_t);
size_t left = size; size_t left = size;
while (sent < size) { char *p = (char *) packet;
int result = send(sd, (const char *) buff + sent, left, 0);
if (result == -1) while (sent < size) {
return -1; ssize_t result = send(sd, p + sent, left, 0);
if (result == -1) return false;
sent += result; sent += result;
left -= sent; left -= sent;
} }
return 0; return true;
}
int net_send_packet(int sd, rc_packet *packet)
{
int len;
int total = 0; // bytes we've sent
int bytesleft; // bytes left to send
int ret = -1;
bytesleft = len = packet->size + sizeof(int);
while (total < len) {
ret = send(sd, (char *) packet + total, bytesleft, 0);
if(ret == -1) break;
total += ret;
bytesleft -= ret;
}
return ret == -1 ? -1 : 1;
} }
rc_packet *net_recv_packet(int sd) rc_packet *net_recv_packet(int sd)
{ {
int psize; int32_t psize;
static rc_packet packet = {0, 0, 0, { 0x00 }}; static rc_packet packet = {0};
// packet.size = packet.id = packet.cmd = 0; ssize_t ret = recv(sd, (char *) &psize, sizeof(psize), 0);
int ret = recv(sd, (char *) &psize, sizeof(int), 0);
if (ret == 0) { if (ret == 0) {
fprintf(stderr, "Connection lost.\n"); log_error("Connection lost.\n");
global_connection_alive = 0; global_connection_alive = 0;
return NULL; return NULL;
} }
if (ret != sizeof(int)) { if (ret != sizeof(psize)) {
fprintf(stderr, "Error: recv() failed. Invalid packet size (%d).\n", ret); log_error("Error: recv() failed.\n");
global_connection_alive = 0; global_connection_alive = 0;
return NULL; return NULL;
} }
// NOTE(Tiiffi): This should fail if size is out of spec! if (psize < MIN_PACKET_SIZE || psize > MAX_PACKET_SIZE) {
if (psize < 10 || psize > DATA_BUFFSIZE) { log_error("Error: Invalid packet size (%d).\n", psize);
fprintf(stderr, "Warning: invalid packet size (%d). Must over 10 and less than %d.\n", psize, DATA_BUFFSIZE); global_connection_alive = 0;
if(psize > DATA_BUFFSIZE || psize < 0) psize = DATA_BUFFSIZE;
net_clean_incoming(sd, psize);
return NULL; return NULL;
} }
packet.size = psize; packet.size = psize;
char *p = (char *) &packet;
int received = 0; int received = 0;
while (received < psize) { while (received < psize) {
ret = recv(sd, (char *) &packet + sizeof(int) + received, psize - received, 0); ret = recv(sd, p + sizeof(int32_t) + received, psize - received, 0);
if (ret == 0) { /* connection closed before completing receving */ if (ret == 0) {
fprintf(stderr, "Connection lost.\n"); log_error("Connection lost.\n");
global_connection_alive = 0; global_connection_alive = 0;
return NULL; return NULL;
} }
@ -460,19 +432,6 @@ rc_packet *net_recv_packet(int sd)
return &packet; return &packet;
} }
int net_clean_incoming(int sd, int size)
{
char tmp[size];
int ret = recv(sd, tmp, size, 0);
if(ret == 0) {
fprintf(stderr, "Connection lost.\n");
global_connection_alive = 0;
}
return ret;
}
void print_color(int color) void print_color(int color)
{ {
// sh compatible color array // sh compatible color array
@ -497,8 +456,9 @@ void print_color(int color)
"\033[4m" /* 16 UNDERLINE 0x6e */ "\033[4m" /* 16 UNDERLINE 0x6e */
}; };
/* 0x72: 'r' */ if (color == 0 || color == 0x72) {
if (color == 0 || color == 0x72) fputs("\033[0m", stdout); /* CANCEL COLOR */ fputs("\033[0m", stdout); // cancel color
}
else else
#endif #endif
{ {
@ -506,7 +466,7 @@ void print_color(int color)
else if (color >= 0x30 && color <= 0x39) else if (color >= 0x30 && color <= 0x39)
color -= 0x30; color -= 0x30;
else if (color == 0x6e) else if (color == 0x6e)
color = 16; /* 0x6e: 'n' */ color = 16;
else return; else return;
#ifndef _WIN32 #ifndef _WIN32
@ -520,80 +480,89 @@ void print_color(int color)
// this hacky mess might use some optimizing // this hacky mess might use some optimizing
void packet_print(rc_packet *packet) void packet_print(rc_packet *packet)
{ {
if (global_raw_output == 1) { uint8_t *data = packet->data;
for (int i = 0; packet->data[i] != 0; ++i)
putchar(packet->data[i]);
if (flag_raw_output == 1) {
for (int i = 0; data[i] != 0; ++i) {
putchar(data[i]);
}
return; return;
} }
int i; int i;
int def_color = 0; int default_color = 0;
#ifdef _WIN32 #ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO console_info; CONSOLE_SCREEN_BUFFER_INFO console_info;
if (GetConsoleScreenBufferInfo(console_handle, &console_info) != 0) { if (GetConsoleScreenBufferInfo(console_handle, &console_info) != 0) {
def_color = console_info.wAttributes + 0x30; default_color = console_info.wAttributes + 0x30;
} else def_color = 0x37; } else default_color = 0x37;
#endif #endif
// colors enabled so try to handle the bukkit colors for terminal // colors enabled so try to handle the bukkit colors for terminal
if (global_disable_colors == 0) { if (flag_disable_colors == 0) {
for (i = 0; (unsigned char) packet->data[i] != 0; ++i) { for (i = 0; data[i] != 0; ++i) {
if (packet->data[i] == 0x0A) print_color(def_color); if (data[i] == 0x0A) print_color(default_color);
else if((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) { else if(data[i] == 0xc2 && data[i + 1] == 0xa7) {
print_color(packet->data[i+=2]); i += 2;
print_color(data[i]);
continue; continue;
} }
putchar(packet->data[i]); putchar(data[i]);
} }
print_color(def_color); // cancel coloring print_color(default_color); // cancel coloring
} }
// strip colors // strip colors
else { else {
for (i = 0; (unsigned char) packet->data[i] != 0; ++i) { for (i = 0; data[i] != 0; ++i) {
if ((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) { if (data[i] == 0xc2 && data[i + 1] == 0xa7) {
i+=2; i += 2;
continue; continue;
} }
putchar(packet->data[i]); putchar(data[i]);
} }
} }
// print newline if string has no newline // print newline if string has no newline
if (packet->data[i-1] != 10 && packet->data[i-1] != 13) putchar('\n'); if (data[i - 1] != 10 && data[i - 1] != 13) {
putchar('\n');
}
} }
rc_packet *packet_build(int id, int cmd, char *s1) rc_packet *packet_build(int id, int cmd, char s[static 1])
{ {
static rc_packet packet = {0, 0, 0, { 0x00 }}; static rc_packet packet = {0};
// size + id + cmd + s1 + s2 NULL terminator // NOTE(Tiiffi): Issue report states that maximum command packet size is 1460 bytes:
int len = strlen(s1); // https://github.com/Tiiffi/mcrcon/issues/45#issuecomment-1000940814
if (len >= DATA_BUFFSIZE) { // https://mctools.readthedocs.io/en/master/rcon.html
fprintf(stderr, "Warning: Command string too long (%d). Maximum allowed: %d.\n", len, DATA_BUFFSIZE - 1); // Have to do some testing to confirm!
int len = strlen(s);
if (len > MAX_COMMAND_LENGTH) {
log_error("Warning: Command string too long (%d). Maximum allowed: %d.\n", len, MAX_COMMAND_LENGTH);
return NULL; return NULL;
} }
packet.size = sizeof(int) * 2 + len + 2; packet.size = sizeof packet.id + sizeof packet.cmd + len + 2;
packet.id = id; packet.id = id;
packet.cmd = cmd; packet.cmd = cmd;
strncpy(packet.data, s1, DATA_BUFFSIZE - 1); if (packet.size > 0) memcpy(packet.data, s, len);
packet.data[len] = 0;
packet.data[len + 1] = 0;
return &packet; return &packet;
} }
int rcon_auth(int sock, char *passwd) bool rcon_auth(int sock, char *passwd)
{ {
int ret;
rc_packet *packet = packet_build(RCON_PID, RCON_AUTHENTICATE, passwd); rc_packet *packet = packet_build(RCON_PID, RCON_AUTHENTICATE, passwd);
if (packet == NULL) if (packet == NULL)
return 0; return 0;
ret = net_send_packet(sock, packet); if (!net_send_packet(sock, packet)) {
if (!ret)
return 0; // send failed return 0; // send failed
}
receive: receive:
packet = net_recv_packet(sock); packet = net_recv_packet(sock);
@ -607,28 +576,36 @@ receive:
goto receive; goto receive;
} }
// return 1 if authentication OK // return true if authentication OK
return packet->id == -1 ? 0 : 1; 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)
{ {
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) {
global_connection_alive = 0; log_error("Error: packet build() failed!\n");
return 0; return 0;
} }
net_send_packet(sock, packet); if (!net_send_packet(sock, packet)) {
log_error("Error: net_send_packet() failed!\n");
return 0;
}
packet = net_recv_packet(sock); packet = net_recv_packet(sock);
if (packet == NULL) if (packet == NULL) {
log_error("Error: net_recv_packet() failed!\n");
return 0; return 0;
}
if (packet->id != RCON_PID) if (packet->id != RCON_PID) {
log_error("Error: invalid packet id!\n");
return 0; return 0;
}
if (!global_silent_mode) { if (!flag_silent_mode) {
if (packet->size > 10) if (packet->size > 10)
packet_print(packet); packet_print(packet);
} }
@ -644,38 +621,41 @@ int run_commands(int argc, char *argv[])
if (!rcon_command(global_rsock, argv[i])) if (!rcon_command(global_rsock, argv[i]))
return EXIT_FAILURE; return EXIT_FAILURE;
if (++i >= argc) i++;
if (i >= argc)
return EXIT_SUCCESS; return EXIT_SUCCESS;
if (global_wait_seconds > 0) { if (flag_wait_seconds > 0) {
#ifdef _WIN32 #ifdef _WIN32
Sleep(global_wait_seconds * 1000); Sleep(flag_wait_seconds * 1000);
#else #else
sleep(global_wait_seconds); sleep(flag_wait_seconds);
#endif #endif
} }
} }
} }
// interactive terminal mode // terminal mode
int run_terminal_mode(int sock) int run_terminal_mode(int sock)
{ {
int ret = 0; char command[MAX_COMMAND_LENGTH] = {0};
char command[DATA_BUFFSIZE] = {0x00};
puts("Logged in.\nType 'Q' or press Ctrl-D / Ctrl-C to disconnect."); puts("Logged in.\nType 'Q' or press Ctrl-D / Ctrl-C to disconnect.");
while (global_connection_alive) { while (global_connection_alive) {
putchar('>'); putchar('>');
int len = get_line(command, DATA_BUFFSIZE); int len = get_line(command, MAX_COMMAND_LENGTH);
if (len < 1) continue; if (len < 1) continue;
if (strcasecmp(command, "Q") == 0) if (strcasecmp(command, "Q") == 0) break;
break;
if (len > 0 && global_connection_alive) if (len > 0 && global_connection_alive) {
ret += rcon_command(sock, command); if (!rcon_command(sock, command)) {
return EXIT_FAILURE;
}
}
/* Special case for "stop" command to prevent server-side bug. /* Special case for "stop" command to prevent server-side bug.
* https://bugs.mojang.com/browse/MC-154617 * https://bugs.mojang.com/browse/MC-154617
@ -687,11 +667,9 @@ int run_terminal_mode(int sock)
if (strcasecmp(command, "stop") == 0) { if (strcasecmp(command, "stop") == 0) {
break; break;
} }
//command[0] = len = 0;
} }
return ret; return EXIT_SUCCESS;
} }
// gets line from stdin and deals with rubbish left in the input buffer // gets line from stdin and deals with rubbish left in the input buffer
@ -700,7 +678,7 @@ int get_line(char *buffer, int bsize)
char *ret = fgets(buffer, bsize, stdin); char *ret = fgets(buffer, bsize, stdin);
if (ret == NULL) { if (ret == NULL) {
if (ferror(stdin)) { if (ferror(stdin)) {
fprintf(stderr, "Error %d: %s\n", errno, strerror(errno)); log_error("Error %d: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
putchar('\n'); putchar('\n');