Merge branch 'develop'

This commit is contained in:
Tiiffi
2020-01-02 02:15:27 +02:00
8 changed files with 352 additions and 396 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@
*.project *.project
mcrcon mcrcon
todo todo
*.plist

View File

@ -1,4 +1,16 @@
#### Version history: #### Version history:
###### 0.7.1
- Add workaround to prevent server-side bug.
https://bugs.mojang.com/browse/MC-154617
###### 0.7.0
- Add -w option for rcon command throttling
* Thanks HorlogeSkynet @ Github
###### 0.6.2
- Set default address to localhost
###### 0.6.1 ###### 0.6.1
- Color coding fixed - Color coding fixed
* Thanks to Hagb @ Github * Thanks to Hagb @ Github
@ -61,19 +73,3 @@
- Packet string data limited to max 2048 (DATA_BUFFSIZE) bytes. - Packet string data limited to max 2048 (DATA_BUFFSIZE) bytes.
No idea how Minecraft handles multiple rcon packets. No idea how Minecraft handles multiple rcon packets.
If someone knows, please mail me so I can implement it. If someone knows, please mail me so I can implement it.
####TODO:
- Make the receive buffer dynamic??
- Change some of the packet size issues to fatal errors.
- Code cleanups.
- Check global variables (remove if possible).
- Add some protocol checks (proper packet id check etc..).
- Preprocessor (#ifdef / #ifndef) cleanups.
- Follow valve rcon protocol standard strictly?
- Multiple packet support if minecraft supports it?!
- Investigate if player chat messages gets sent through rcon.
If they are, the messaging system requires rewriting.
- Name resolving should be integrated to connection creation function.
- Dont try to cleanup the socket if not authenticated
- Better sockets error reporting
- Better error function (VA_ARGS support)

View File

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

View File

@ -1,4 +1,4 @@
Copyright (c) 2012-2016, Tiiffi <tiiffi_at_gmail_dot_com> Copyright (c) 2012-2019, 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
@ -18,3 +18,4 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source 3. This notice may not be removed or altered from any source
distribution. distribution.

View File

@ -1,19 +1,23 @@
# if you want to cross compile # if you want to cross compile:
# export PATH=$PATH:/path/to/compiler/bin # export PATH=$PATH:/path/to/compiler/bin
# export CROSS_COMPILE=arm-none-linux-gnueabi- # export CROSS_COMPILE=arm-none-linux-gnueabi-
# make # make
#
# Windows cross compile:
# i686-w64-mingw32-gcc -std=gnu99 -Wall -Wextra -Wpedantic -Os -s -o mcrcon.exe mcrcon.c -lws2_32
EXENAME = mcrcon EXENAME = mcrcon
PREFIX ?= /usr/local PREFIX ?= /usr/local
EXTRAFLAGS ?= -fstack-protector-strong
INSTALL = install INSTALL = install
LINKER = LINKER =
RM = rm -f RM = rm -v -f
CC = gcc
CFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -Os -s
EXTRAFLAGS ?= -fstack-protector-strong
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
CC = gcc
LINKER = -lws2_32 LINKER = -lws2_32
EXENAME = mcrcon.exe EXENAME = mcrcon.exe
RM = cmd /C del /F RM = cmd /C del /F
@ -21,9 +25,7 @@ endif
ifeq ($(shell uname), Darwin) ifeq ($(shell uname), Darwin)
INSTALL = ginstall INSTALL = ginstall
CFLAGS ?= -std=gnu99 -Wall -Wextra -Wpedantic -Os CFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -Os
else
CFLAGS ?= -std=gnu99 -Wall -Wextra -Wpedantic -Os -s
endif endif
.PHONY: all .PHONY: all
@ -35,13 +37,13 @@ $(EXENAME): mcrcon.c
ifneq ($(OS), Windows_NT) ifneq ($(OS), Windows_NT)
.PHONY: install .PHONY: install
install: install:
$(INSTALL) -vD $(EXENAME) $(PREFIX)/bin/$(EXENAME) $(INSTALL) -vD $(EXENAME) $(DESTDIR)$(PREFIX)/bin/$(EXENAME)
$(INSTALL) -vD -m 0644 mcrcon.1 $(PREFIX)/share/man/man1/mcrcon.1 $(INSTALL) -vD -m 0644 mcrcon.1 $(DESTDIR)$(PREFIX)/share/man/man1/mcrcon.1
@echo "\nmcrcon installed. Run 'make uninstall' if you want to uninstall.\n" @echo "\nmcrcon installed. Run 'make uninstall' if you want to uninstall.\n"
.PHONY: uninstall .PHONY: uninstall
uninstall: uninstall:
rm -f $(PREFIX)/bin/$(EXENAME) $(PREFIX)/share/man/man1/mcrcon.1 $(RM) $(DESTDIR)$(PREFIX)/bin/$(EXENAME) $(DESTDIR)$(PREFIX)/share/man/man1/mcrcon.1
@echo "\nmcrcon uninstalled.\n" @echo "\nmcrcon uninstalled.\n"
endif endif

View File

@ -1,15 +1,31 @@
<<<<<<< HEAD
### Installing:
=======
# mcrcon
>>>>>>> develop
mcrcon is console based Minecraft [rcon](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) client for remote administration and server maintenance scripts.
---
### Installing: ### Installing:
from sources: ##### via packet manager:
See https://pkgs.org/download/mcrcon for available packages in various Linux distros (note that available packages might be outdated).
- Gentoo Linux: https://packages.gentoo.org/packages/games-util/mcrcon
- Arch Linux: https://aur.archlinux.org/packages/mcrcon/
##### 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
make make
sudo make install sudo make install
``` ```
Check **INSTALL** for more details. Check [INSTALL.md](INSTALL.md) for more details.
You can also download precompiled binaries*: https://github.com/Tiiffi/mcrcon/releases/latest Precompiled binaries (if provided)*: https://github.com/Tiiffi/mcrcon/releases/latest
<sub>*At the moment binaries are provided for Linux and Windows.</sub> <sub>*At the moment binaries are provided for Linux and Windows.</sub>
@ -22,33 +38,42 @@ Sends rcon commands to Minecraft server.
``` ```
Option: Option:
-h Print usage -H Server address (default: localhost)
-H Server address -P Port (default: 25575)
-P Port (default is 25575)
-p Rcon password -p Rcon password
-t Interactive terminal mode -t Terminal mode
-s Silent mode (do not print received packets) -s Silent mode
-c Disable colors -c Disable colors
-r Output raw packets (debugging and custom handling) -r Output raw packets
-v Output version information -w Wait for specified duration (seconds) between each command (1 - 600s)
-h Print usage
-v Version information
``` ```
Commands with arguments must be enclosed in quotes.
Server address, port and password can be set using following environment variables: Server address, port and password can be set using following environment variables:
``` ```
MCRCON_HOST MCRCON_HOST
MCRCON_PORT MCRCON_PORT
MCRCON_PASS MCRCON_PASS
``` ```
Note that command-line options will override environment variables. ###### Notes:
- mcrcon will start in terminal mode if no commands are given
- Command-line options will override environment variables
- Rcon commands with spaces must be enclosed in quotes
Example: Example:
```mcrcon -H my.minecraft.server -p password "say Server is restarting!" save-all stop``` > 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```
--- ---
<<<<<<< HEAD
### Enable rcon on server ### Enable rcon on server
Remember to enable rcon by adding following lines to ```server.properties``` file. Remember to enable rcon by adding following lines to ```server.properties``` file.
=======
##### Enable rcon on server
Remember to enable rcon by adding following lines to [```server.properties```](https://minecraft.gamepedia.com/Server.properties) file.
>>>>>>> develop
``` ```
enable-rcon=true enable-rcon=true
rcon.port=25575 rcon.port=25575
@ -57,14 +82,24 @@ rcon.password=your_rcon_pasword
--- ---
<<<<<<< HEAD
#### Contact: #### Contact:
=======
##### Contact:
>>>>>>> develop
* WWW: https://github.com/Tiiffi/mcrcon/ * WWW: https://github.com/Tiiffi/mcrcon/
* MAIL: tiiffi_at_gmail_dot_com * MAIL: tiiffi at gmail
* IRC: tiiffi @ quakenet * IRC: tiiffi @ quakenet
* BUG REPORTS: https://github.com/Tiiffi/mcrcon/issues/ * BUG REPORTS: https://github.com/Tiiffi/mcrcon/issues/
--- ---
### License
This project is licensed under the zlib License - see the [LICENSE](LICENSE) file for details.
---
<sub>Master:</sub> ![Master build](https://api.travis-ci.org/Tiiffi/mcrcon.svg?branch=master) <sub>Master:</sub> ![Master build](https://api.travis-ci.org/Tiiffi/mcrcon.svg?branch=master)
<sub>Develop:</sub> ![Develop build](https://api.travis-ci.org/Tiiffi/mcrcon.svg?branch=develop) <sub>Develop:</sub> ![Develop build](https://api.travis-ci.org/Tiiffi/mcrcon.svg?branch=develop)

View File

@ -1,9 +1,9 @@
.\" Process this file with .\" Process this file with
.\" groff -man -Tascii mcrcon.1 .\" groff -man -Tascii mcrcon.1
.\" .\"
.TH MCRCON 1 "December 2016" "Version 0.6.1" .TH MCRCON 1 "December 2019" "Version 0.7.1"
.SH NAME .SH NAME
mcrcon \- sends rcon commands to a Minecraft server mcrcon \- send rcon commands to a Minecraft server
.SH SYNOPSIS .SH SYNOPSIS
.B mcrcon [ .B mcrcon [
options options
@ -11,31 +11,34 @@ options
commands commands
.B ] .B ]
.SH DESCRIPTION .SH DESCRIPTION
mcrcon is Minecraft rcon client / terminal with bukkit coloring support. mcrcon is Minecraft rcon client for remote administration and server maintenance scripts.
It is well suited for remote administration and server maintenance scripts.
.SH OPTIONS .SH OPTIONS
.IP -h
Print usage
.IP -H .IP -H
Server address Server address (default: localhost)
.IP -P .IP -P
Port (default is 25575) Port (default: 25575)
.IP -p .IP -p
Rcon password Rcon password
.IP -t .IP -t
Interactive terminal mode Terminal mode
.IP -s .IP -s
Silent mode (do not print received packets) Silent mode
.IP -c .IP -c
Disable colors Disable colors
.IP -r .IP -r
Output raw packets (for debugging and custom handling) Output raw packets
.IP -w
Wait for specified duration (seconds) between each command (1 - 600s)
.IP -h
Print usage
.IP -v .IP -v
Output version information Output version information
.PP .PP
Commands with arguments must be enclosed in quotes. Commands with spaces must be enclosed in quotes.
.SH ENVIRONMENTAL VARIABLES .br
Server address, port and password can be set using following environment variables: mcrcon will start in terminal mode if no commands are given.
.SH ENVIRONMENT VARIABLES
Server address, port and password can be set with following environment variables:
.PP .PP
\fBMCRCON_HOST \fBMCRCON_HOST
.br .br
@ -55,9 +58,9 @@ Send "weather clear" command to server using custom port 1337
\fBmcrcon\fR -H my.minecraft.server -P 1337 -p password "weather clear" \fBmcrcon\fR -H my.minecraft.server -P 1337 -p password "weather clear"
.RE .RE
.PP .PP
Send three commands to server (say, save-all and stop) Send three commands ("say", "save-all", "stop") and wait five seconds between the commands.
.RS .RS
\fBmcrcon\fR -H my.minecraft.server -p password "say Server is restarting!" save-all stop \fBmcrcon\fR -H my.minecraft.server -p password -w 5 "say Server is restarting!" save-all stop
.RE .RE
.SH BUGS .SH BUGS
Bugs can be reported to \fBtiiffi_at_gmail_dot_com\fR or \fBhttps://github.com/Tiiffi/mcrcon/issues/\fR Report bugs to \fBtiiffi+mcrcon at gmail\fR or \fBhttps://github.com/Tiiffi/mcrcon/issues/\fR

486
mcrcon.c
View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2012-2016, Tiiffi <tiiffi -> gmail_dot_com> * Copyright (c) 2012-2019, 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
@ -29,10 +29,11 @@
#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 // for name resolving on windows
// enable this if you get compiler whine about getaddrinfo on windows // enable this if you get compiler whine about getaddrinfo() on windows
//#define _WIN32_WINNT 0x0501 //#define _WIN32_WINNT 0x0501
#include <ws2tcpip.h> #include <ws2tcpip.h>
@ -46,7 +47,7 @@
#include <netdb.h> #include <netdb.h>
#endif #endif
#define VERSION "0.6.1" #define VERSION "0.7.1"
#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__")"
@ -56,8 +57,7 @@
#define RCON_AUTH_RESPONSE 2 #define RCON_AUTH_RESPONSE 2
#define RCON_PID 0xBADC0DE #define RCON_PID 0xBADC0DE
// a bit too big perhaps? #define DATA_BUFFSIZE 4096
#define DATA_BUFFSIZE 10240
// rcon packet structure // rcon packet structure
typedef struct _rc_packet { typedef struct _rc_packet {
@ -65,17 +65,14 @@ typedef struct _rc_packet {
int id; int id;
int cmd; int cmd;
char data[DATA_BUFFSIZE]; char data[DATA_BUFFSIZE];
// ignoring string2 atm. // ignoring string2 for now
} rc_packet; } rc_packet;
// =================================== // ===================================
// FUNCTION DEFINITIONS // FUNCTION DEFINITIONS
// =================================== // ===================================
// endianness related functions
bool is_bigendian(void);
int32_t reverse_int32(int32_t n);
// Network related functions // Network related functions
#ifdef _WIN32 #ifdef _WIN32
void net_init_WSA(void); void net_init_WSA(void);
@ -93,26 +90,25 @@ void usage(void);
void print_color(int color); void print_color(int color);
#endif #endif
int get_line(char *buffer, int len); int get_line(char *buffer, int len);
int run_terminal_mode(int rsock); 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 *s1);
uint8_t *packet_build_malloc(size_t *size, int32_t id, int32_t cmd, char *string);
void packet_print(rc_packet *packet); void packet_print(rc_packet *packet);
int rcon_auth(int sock, char *passwd);
int rcon_auth(int rsock, char *passwd); int rcon_command(int sock, char *command);
int rcon_command(int rsock, char *command);
// ============================================= // =============================================
// GLOBAL VARIABLES // GLOBAL VARIABLES
// ============================================= // =============================================
static int raw_output = 0; static int global_raw_output = 0;
static int silent_mode = 0; static int global_silent_mode = 0;
static int print_colors = 1; static int global_disable_colors = 0;
static int connection_alive = 1; static int global_connection_alive = 1;
static int rsock; static int global_rsock;
static int global_wait_seconds = 0;
#ifdef _WIN32 #ifdef _WIN32
// console coloring on windows // console coloring on windows
@ -122,85 +118,96 @@ static int rsock;
// safety stuff (windows is still misbehaving) // safety stuff (windows is still misbehaving)
void exit_proc(void) void exit_proc(void)
{ {
if (rsock != -1) if (global_rsock != -1)
net_close(rsock); net_close(global_rsock);
} }
// Check windows & linux behaviour !!! // Check windows & linux behaviour !!!
void sighandler(/*int sig*/) void sighandler(/*int sig*/)
{ {
connection_alive = 0; global_connection_alive = 0;
#ifndef _WIN32 #ifndef _WIN32
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
#endif #endif
} }
#define MAX_WAIT_TIME 600
unsigned int mcrcon_parse_seconds(char *str)
{
char *end;
long result = strtol(str, &end, 10);
if (errno != 0) {
fprintf(stderr, "-w invalid value.\nerror %d: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
if (end == str) {
fprintf(stderr, "-w invalid value (not a number?)\n");
exit(EXIT_FAILURE);
}
if (result <= 0 || result > MAX_WAIT_TIME) {
fprintf(stderr, "-w value out of range.\nAcceptable value is 1 - %d (seconds).\n", MAX_WAIT_TIME);
exit(EXIT_FAILURE);
}
return (unsigned int) result;
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int opt;
int terminal_mode = 0; int terminal_mode = 0;
char *host = getenv("MCRCON_HOST"); char *host = getenv("MCRCON_HOST");
char *pass = getenv("MCRCON_PASS"); char *pass = getenv("MCRCON_PASS");
char *port = getenv("MCRCON_PORT"); char *port = getenv("MCRCON_PORT");
if (!port) if (!port) port = "25575";
port = "25575"; if (!host) host = "localhost";
if(argc < 2 && host == NULL && pass == NULL) if(argc < 1 && pass == NULL) usage();
usage();
// default getopt error handler enabled // default getopt error handler enabled
opterr = 1; opterr = 1;
int opt;
while ((opt = getopt(argc, argv, "vrtcshH:p:P:i")) != -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': case 'c': global_disable_colors = 1; break;
case 'c': print_colors = 0; break; case 's': global_silent_mode = 1; break;
case 'S': case 'i': /* reserved for interp mode */break;
case 's': silent_mode = 1; break; case 't': terminal_mode = 1; break;
case 'T': case 'r': global_raw_output = 1; break;
case 't': case 'w':
case 'I': global_wait_seconds = mcrcon_parse_seconds(optarg);
case 'i': terminal_mode = 1; break;
case 'r': raw_output = 1; break;
case 'v':
puts(VER_STR"\nhttps://github.com/Tiiffi/mcrcon");
exit(EXIT_SUCCESS);
break; break;
case 'h':
case '?': usage(); break;
/*
if(optopt == 'P' || optopt == 'H' || optopt == 'p')
fprintf (stderr, "Option -%c requires an argument.\n\n", optopt);
else fprintf (stderr, "Unknown option -%c\n\n", optopt);
*/
default: exit(EXIT_FAILURE); case 'v':
puts(VER_STR" - https://github.com/Tiiffi/mcrcon");
puts("Bug reports:\n\ttiiffi+mcrcon at gmail\n\thttps://github.com/Tiiffi/mcrcon/issues/");
exit(EXIT_SUCCESS);
case 'h': usage(); break;
case '?':
default:
puts("Try 'mcrcon -h' or 'man mcrcon' for help.");
exit(EXIT_FAILURE);
} }
} }
if (host == NULL) if (pass == NULL) {
{ puts("You must give password (-p password).\nTry 'mcrcon -h' or 'man mcrcon' for help.");
fputs("Host not defined (-H flag). Try 'mcrcon -h' or 'man mcrcon' for more information.\n\n", stdout);
return 0;
}
if (pass == NULL)
{
fputs("Password not defined (-p flag). Try 'mcrcon -h' 'man mcrcon' for more information.\n\n", stdout);
return 0; return 0;
} }
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);
signal(SIGABRT, &sighandler); signal(SIGABRT, &sighandler);
@ -210,55 +217,61 @@ int main(int argc, char *argv[])
#ifdef _WIN32 #ifdef _WIN32
net_init_WSA(); net_init_WSA();
console_handle = GetStdHandle(STD_OUTPUT_HANDLE); console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle == INVALID_HANDLE_VALUE) console_handle = NULL; if (console_handle == INVALID_HANDLE_VALUE)
console_handle = NULL;
#endif #endif
// open socket // open socket
rsock = net_connect(host, port); global_rsock = net_connect(host, port);
int exit_code = EXIT_SUCCESS;
// auth & commands // auth & commands
if (rcon_auth(rsock, pass)) if (rcon_auth(global_rsock, pass)) {
{
if (terminal_mode) if (terminal_mode)
run_terminal_mode(rsock); run_terminal_mode(global_rsock);
else else
run_commands(argc, argv); exit_code = run_commands(argc, argv);
} }
else // auth failed else { // auth failed
fprintf(stdout, "Authentication failed!\n"); fprintf(stdout, "Authentication failed!\n");
exit_code = EXIT_FAILURE;
}
net_close(rsock); net_close(global_rsock);
rsock = -1; global_rsock = -1;
return EXIT_SUCCESS; return exit_code;
} }
void usage(void) void usage(void)
{ {
fputs( puts(
"Usage: "IN_NAME" [OPTIONS]... [COMMANDS]...\n\n" "Usage: "IN_NAME" [OPTIONS] [COMMANDS]\n\n"
"Sends rcon commands to Minecraft server.\n\n" "Send rcon commands to Minecraft server.\n\n"
"Option:\n" "Options:\n"
" -h\t\tPrint usage\n" " -H\t\tServer address (default: localhost)\n"
" -H\t\tServer address\n" " -P\t\tPort (default: 25575)\n"
" -P\t\tPort (default is 25575)\n"
" -p\t\tRcon password\n" " -p\t\tRcon password\n"
" -t\t\tInteractive terminal mode\n" " -t\t\tTerminal mode\n"
" -s\t\tSilent mode (do not print received packets)\n" " -s\t\tSilent mode\n"
" -c\t\tDisable colors\n" " -c\t\tDisable colors\n"
" -r\t\tOutput raw packets (debugging and custom handling)\n" " -r\t\tOutput raw packets\n"
" -v\t\tOutput version information\n\n" " -w\t\tWait for specified duration (seconds) between each command (1 - 600s)\n"
"Server address, port and password can be set using following environment variables:\n" " -h\t\tPrint usage\n"
" -v\t\tVersion information\n\n"
"Server address, port and password can be set with following environment variables:\n"
" MCRCON_HOST\n" " MCRCON_HOST\n"
" MCRCON_PORT\n" " MCRCON_PORT\n"
" MCRCON_PASS\n\n" " MCRCON_PASS\n"
,stdout
); );
puts("Command-line options will override environment variables."); puts (
puts("Rcon commands with arguments must be enclosed in quotes.\n"); "- mcrcon will start in terminal mode if no commands are given\n"
puts("Example:\n\t"IN_NAME" -H my.minecraft.server -p password \"say Server is restarting!\" save-all stop\n"); "- Command-line options will override environment variables\n"
puts(VER_STR"\nReport bugs to tiiffi_at_gmail_dot_com or https://github.com/Tiiffi/mcrcon/issues/\n"); "- Rcon commands with spaces must be enclosed in quotes\n"
);
puts("Example:\n\t"IN_NAME" -H my.minecraft.server -p password -w 5 \"say Server is restarting!\" save-all stop\n");
#ifdef _WIN32 #ifdef _WIN32
puts("Press enter to exit."); puts("Press enter to exit.");
@ -278,8 +291,7 @@ void net_init_WSA(void)
WORD version = MAKEWORD(2, 2); WORD version = MAKEWORD(2, 2);
int err = WSAStartup(version, &wsadata); int err = WSAStartup(version, &wsadata);
if (err != 0) if (err != 0) {
{
fprintf(stderr, "WSAStartup failed. Error: %d.\n", err); fprintf(stderr, "WSAStartup failed. Error: %d.\n", err);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -317,27 +329,26 @@ int net_connect(const char *host, const char *port)
#endif #endif
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"); fprintf(stderr, "Name resolution failed.\n");
#ifdef _WIN32 #ifdef _WIN32
fprintf(stderr, "Error %d: %s", ret, gai_strerror(ret)); fprintf(stderr, "Error %d: %s", ret, gai_strerror(ret));
#else #else
fprintf(stderr, "Error %d: %s\n", ret, gai_strerror(ret)); fprintf(stderr, "Error %d: %s\n", ret, gai_strerror(ret));
#endif #endif
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// Go through the hosts and try to connect // Go through the hosts and try to connect
for (p = server_info; p != NULL; p = p->ai_next) for (p = server_info; p != NULL; p = p->ai_next) {
{
sd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); sd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sd == -1) if (sd == -1)
continue; continue;
ret = connect(sd, p->ai_addr, p->ai_addrlen); ret = connect(sd, p->ai_addr, p->ai_addrlen);
if (ret == -1) if (ret == -1) {
{
net_close(sd); net_close(sd);
continue; continue;
} }
@ -345,19 +356,18 @@ int net_connect(const char *host, const char *port)
break; break;
} }
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"); fprintf(stderr, "Connection failed.\n");
#ifndef _WIN32 #ifndef _WIN32
fprintf(stderr, "Error %d: %s\n", errno, strerror(errno)); fprintf(stderr, "Error %d: %s\n", errno, strerror(errno));
#endif #endif
freeaddrinfo(server_info); freeaddrinfo(server_info);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
freeaddrinfo(server_info); freeaddrinfo(server_info);
return sd; return sd;
} }
@ -366,11 +376,11 @@ int net_send(int sd, const uint8_t *buff, size_t size)
size_t sent = 0; size_t sent = 0;
size_t left = size; size_t left = size;
while (sent < size) while (sent < size) {
{
int result = send(sd, (const char *) buff + sent, left, 0); int result = send(sd, (const char *) buff + sent, left, 0);
if (result == -1) return -1; if (result == -1)
return -1;
sent += result; sent += result;
left -= sent; left -= sent;
@ -388,10 +398,9 @@ int net_send_packet(int sd, rc_packet *packet)
bytesleft = len = packet->size + sizeof(int); bytesleft = len = packet->size + sizeof(int);
while(total < len) while (total < len) {
{
ret = send(sd, (char *) packet + total, bytesleft, 0); ret = send(sd, (char *) packet + total, bytesleft, 0);
if(ret == -1) { break; } if(ret == -1) break;
total += ret; total += ret;
bytesleft -= ret; bytesleft -= ret;
} }
@ -408,22 +417,19 @@ rc_packet *net_recv_packet(int sd)
int ret = recv(sd, (char *) &psize, sizeof(int), 0); int ret = recv(sd, (char *) &psize, sizeof(int), 0);
if (ret == 0) if (ret == 0) {
{
fprintf(stderr, "Connection lost.\n"); fprintf(stderr, "Connection lost.\n");
connection_alive = 0; global_connection_alive = 0;
return NULL; return NULL;
} }
if (ret != sizeof(int)) if (ret != sizeof(int)) {
{
fprintf(stderr, "Error: recv() failed. Invalid packet size (%d).\n", ret); fprintf(stderr, "Error: recv() failed. Invalid packet size (%d).\n", ret);
connection_alive = 0; global_connection_alive = 0;
return NULL; return NULL;
} }
if (psize < 10 || psize > DATA_BUFFSIZE) if (psize < 10 || psize > DATA_BUFFSIZE) {
{
fprintf(stderr, "Warning: invalid packet size (%d). Must over 10 and less than %d.\n", psize, DATA_BUFFSIZE); fprintf(stderr, "Warning: invalid packet size (%d). Must over 10 and less than %d.\n", psize, DATA_BUFFSIZE);
if(psize > DATA_BUFFSIZE || psize < 0) psize = DATA_BUFFSIZE; if(psize > DATA_BUFFSIZE || psize < 0) psize = DATA_BUFFSIZE;
@ -434,19 +440,16 @@ rc_packet *net_recv_packet(int sd)
packet.size = psize; packet.size = psize;
ret = recv(sd, (char *) &packet + sizeof(int), psize, 0); int received = 0;
if (ret == 0) while (received < psize) {
{ ret = recv(sd, (char *) &packet + sizeof(int) + received, psize - received, 0);
if (ret == 0) { /* connection closed before completing receving */
fprintf(stderr, "Connection lost.\n"); fprintf(stderr, "Connection lost.\n");
connection_alive = 0; global_connection_alive = 0;
return NULL; return NULL;
} }
if(ret != psize) received += ret;
{
fprintf(stderr, "Warning: recv() return value (%d) does not match expected packet size (%d).\n", ret, psize);
net_clean_incoming(sd, DATA_BUFFSIZE); /* Should be enough. Needs some checking */
return NULL;
} }
return &packet; return &packet;
@ -455,13 +458,11 @@ rc_packet *net_recv_packet(int sd)
int net_clean_incoming(int sd, int size) int net_clean_incoming(int sd, int size)
{ {
char tmp[size]; char tmp[size];
int ret = recv(sd, tmp, size, 0); int ret = recv(sd, tmp, size, 0);
if(ret == 0) if(ret == 0) {
{
fprintf(stderr, "Connection lost.\n"); fprintf(stderr, "Connection lost.\n");
connection_alive = 0; global_connection_alive = 0;
} }
return ret; return ret;
@ -471,8 +472,7 @@ void print_color(int color)
{ {
// sh compatible color array // sh compatible color array
#ifndef _WIN32 #ifndef _WIN32
char *colors[] = char *colors[] = {
{
"\033[0;30m", /* 00 BLACK 0x30 */ "\033[0;30m", /* 00 BLACK 0x30 */
"\033[0;34m", /* 01 BLUE 0x31 */ "\033[0;34m", /* 01 BLUE 0x31 */
"\033[0;32m", /* 02 GREEN 0x32 */ "\033[0;32m", /* 02 GREEN 0x32 */
@ -492,16 +492,16 @@ void print_color(int color)
"\033[4m" /* 16 UNDERLINE 0x6e */ "\033[4m" /* 16 UNDERLINE 0x6e */
}; };
if(color == 0 || color == 0x72) /* 0x72: 'r' */ /* 0x72: 'r' */
{ if (color == 0 || color == 0x72) fputs("\033[0m", stdout); /* CANCEL COLOR */
fputs("\033[0m", stdout); /* CANCEL COLOR */
}
else else
#endif #endif
{ {
if (color >= 0x61 && color <= 0x66) color -= 0x57; if (color >= 0x61 && color <= 0x66) color -= 0x57;
else if(color >= 0x30 && color <= 0x39) color -= 0x30; else if (color >= 0x30 && color <= 0x39)
else if(color == 0x6e) color=16; /* 0x6e: 'n' */ color -= 0x30;
else if (color == 0x6e)
color = 16; /* 0x6e: 'n' */
else return; else return;
#ifndef _WIN32 #ifndef _WIN32
@ -515,9 +515,10 @@ 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 (raw_output == 1) if (global_raw_output == 1) {
{ for (int i = 0; packet->data[i] != 0; ++i)
for (int i = 0; packet->data[i] != 0; ++i) putchar(packet->data[i]); putchar(packet->data[i]);
return; return;
} }
@ -526,18 +527,14 @@ void packet_print(rc_packet *packet)
#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; def_color = console_info.wAttributes + 0x30;
else } else def_color = 0x37;
def_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 (print_colors == 1) if (global_disable_colors == 0) {
{ for (i = 0; (unsigned char) packet->data[i] != 0; ++i) {
for (i = 0; (unsigned char) packet->data[i] != 0; ++i)
{
if (packet->data[i] == 0x0A) print_color(def_color); if (packet->data[i] == 0x0A) print_color(def_color);
else if((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) { else if((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) {
print_color(packet->data[i+=2]); print_color(packet->data[i+=2]);
@ -548,10 +545,8 @@ void packet_print(rc_packet *packet)
print_color(def_color); // cancel coloring print_color(def_color); // cancel coloring
} }
// strip colors // strip colors
else else {
{ for (i = 0; (unsigned char) packet->data[i] != 0; ++i) {
for (i = 0; (unsigned char) packet->data[i] != 0; ++i)
{
if ((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) { if ((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) {
i+=2; i+=2;
continue; continue;
@ -561,8 +556,7 @@ void packet_print(rc_packet *packet)
} }
// 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) if (packet->data[i-1] != 10 && packet->data[i-1] != 13) putchar('\n');
putchar('\n');
} }
rc_packet *packet_build(int id, int cmd, char *s1) rc_packet *packet_build(int id, int cmd, char *s1)
@ -571,8 +565,7 @@ rc_packet *packet_build(int id, int cmd, char *s1)
// size + id + cmd + s1 + s2 NULL terminator // size + id + cmd + s1 + s2 NULL terminator
int s1_len = strlen(s1); int s1_len = strlen(s1);
if (s1_len > DATA_BUFFSIZE) if (s1_len > DATA_BUFFSIZE) {
{
fprintf(stderr, "Warning: Command string too long (%d). Maximum allowed: %d.\n", s1_len, DATA_BUFFSIZE); fprintf(stderr, "Warning: Command string too long (%d). Maximum allowed: %d.\n", s1_len, DATA_BUFFSIZE);
return NULL; return NULL;
} }
@ -585,67 +578,7 @@ rc_packet *packet_build(int id, int cmd, char *s1)
return &packet; return &packet;
} }
// TODO(Tiiffi): String length limit? int rcon_auth(int sock, char *passwd)
uint8_t *packet_build_malloc(size_t *size, int32_t id, int32_t cmd, char *string)
{
size_t string_length = strlen(string);
*size = 3 * sizeof(int32_t) + string_length + 2;
uint8_t *packet = malloc(*size);
if (packet == NULL) return NULL;
int32_t *p = (int32_t *) packet;
p[0] = (int32_t) *size - sizeof(int32_t);
p[1] = id;
p[2] = cmd;
memcpy(&p[3], string, string_length);
packet[12 + string_length] = 0;
packet[13 + string_length] = 0;
return packet;
}
// rcon packet structure
#define MAX_PACKET_SIZE (size_t) 1460 // including size member
#define MIN_PACKET_SIZE (size_t) 10
#define MAX_STRING_SIZE (size_t) (MAX_PACKET_SIZE - 2 - 3 * sizeof(int32_t))
#define SIZEOF_PACKET(x) (size_t) (x.size + sizeof(int32_t))
struct rcon_packet
{
int32_t size;
int32_t id;
int32_t cmd;
uint8_t data[MAX_STRING_SIZE];
};
struct rcon_packet packet_build_new(int32_t id, int32_t cmd, char *string)
{
struct rcon_packet packet;
size_t string_length = strlen(string);
if (string_length > MAX_STRING_SIZE)
{
string_length = MAX_STRING_SIZE;
fprintf(stderr,
"Warning: command string is too long. Truncating to "
"%u characters.\n", (unsigned) MAX_STRING_SIZE
);
}
packet.size = 2 * sizeof(int32_t) + string_length + 2;
packet.id = id;
packet.cmd = cmd;
memcpy(packet.data, string, string_length);
packet.data[string_length] = 0;
packet.data[string_length + 1] = 0;
return packet;
}
int rcon_auth(int rsock, char *passwd)
{ {
int ret; int ret;
@ -653,11 +586,11 @@ int rcon_auth(int rsock, char *passwd)
if (packet == NULL) if (packet == NULL)
return 0; return 0;
ret = net_send_packet(rsock, packet); ret = net_send_packet(sock, packet);
if (!ret) if (!ret)
return 0; // send failed return 0; // send failed
packet = net_recv_packet(rsock); packet = net_recv_packet(sock);
if (packet == NULL) if (packet == NULL)
return 0; return 0;
@ -665,41 +598,24 @@ int rcon_auth(int rsock, char *passwd)
return packet->id == -1 ? 0 : 1; return packet->id == -1 ? 0 : 1;
} }
int rcon_command(int rsock, char *command) int rcon_command(int sock, char *command)
{ {
int ret; (void) ret; rc_packet *packet = packet_build(RCON_PID, RCON_EXEC_COMMAND, command);
if (packet == NULL) {
size_t size; global_connection_alive = 0;
uint8_t *p = packet_build_malloc(&size, RCON_PID, RCON_EXEC_COMMAND, command);
if (p == NULL)
{
connection_alive = 0;
return 0; return 0;
} }
net_send(rsock, p, size); net_send_packet(sock, packet);
free(p); packet = net_recv_packet(sock);
//ret = net_send_packet(rsock, packet);
//if(!ret) return 0; /* send failed */
rc_packet *packet;
packet = net_recv_packet(rsock);
if (packet == NULL) if (packet == NULL)
return 0; return 0;
if (packet->id != RCON_PID) if (packet->id != RCON_PID)
return 0; return 0;
if (!silent_mode) if (!global_silent_mode) {
{
/*
if(packet->size == 10) {
printf("Unknown command \"%s\". Type \"help\" or \"?\" for help.\n", command);
}
else
*/
if (packet->size > 10) if (packet->size > 10)
packet_print(packet); packet_print(packet);
} }
@ -709,33 +625,56 @@ int rcon_command(int rsock, char *command)
int run_commands(int argc, char *argv[]) int run_commands(int argc, char *argv[])
{ {
int i, ok = 1, ret = 0; int i = optind;
for (i = optind; i < argc && ok; i++) for (;;) {
{ if (!rcon_command(global_rsock, argv[i]))
ok = rcon_command(rsock, argv[i]); return EXIT_FAILURE;
ret += ok;
if (++i >= argc)
return EXIT_SUCCESS;
if (global_wait_seconds > 0) {
#ifdef _WIN32
Sleep(global_wait_seconds * 1000);
#else
sleep(global_wait_seconds);
#endif
}
} }
return ret;
} }
// interactive terminal mode // interactive terminal mode
int run_terminal_mode(int rsock) int run_terminal_mode(int sock)
{ {
int ret = 0; int ret = 0;
char command[DATA_BUFFSIZE] = {0x00}; char command[DATA_BUFFSIZE] = {0x00};
puts("Logged in. Type \"Q\" to quit!"); puts("Logged in. Type 'quit' or 'exit' to quit.");
while (connection_alive) while (global_connection_alive) {
{ putchar('>');
int len = get_line(command, DATA_BUFFSIZE); int len = get_line(command, DATA_BUFFSIZE);
if ((strcasecmp(command, "exit") && strcasecmp(command, "quit")) == 0)
break;
if(command[0] == 'Q' && command[1] == 0) if(command[0] == 'Q' && command[1] == 0)
break; break;
if(len > 0 && connection_alive) if(len > 0 && global_connection_alive)
ret += rcon_command(rsock, command); ret += rcon_command(sock, command);
/* Special case for "stop" command to prevent server-side bug.
* https://bugs.mojang.com/browse/MC-154617
*
* NOTE: This is hacky workaround which should be handled better to
* ensure compatibility with other servers using source RCON.
* NOTE: strcasecmp() is POSIX function.
*/
if (strcasecmp(command, "stop") == 0) {
break;
}
command[0] = len = 0; command[0] = len = 0;
} }
@ -746,44 +685,23 @@ int run_terminal_mode(int rsock)
// 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
int get_line(char *buffer, int bsize) int get_line(char *buffer, int bsize)
{ {
int ch, len; char *ret = fgets(buffer, bsize, stdin);
if (ret == NULL)
fputs(">", stdout); exit(EXIT_FAILURE);
(void) fgets(buffer, bsize, stdin);
if (buffer[0] == 0) if (buffer[0] == 0)
connection_alive = 0; global_connection_alive = 0;
// remove unwanted characters from the buffer // remove unwanted characters from the buffer
buffer[strcspn(buffer, "\r\n")] = '\0'; buffer[strcspn(buffer, "\r\n")] = '\0';
len = strlen(buffer); int len = strlen(buffer);
// clean input buffer if needed // clean input buffer if needed
if (len == bsize - 1) if (len == bsize - 1) {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF); while ((ch = getchar()) != '\n' && ch != EOF);
}
return len; return len;
} }
// http://www.ibm.com/developerworks/aix/library/au-endianc/
bool is_bigendian(void)
{
const int32_t n = 1;
if (*(uint8_t *) &n == 0 ) return true;
return false;
}
int32_t reverse_int32(int32_t n)
{
int32_t tmp;
uint8_t *t = (uint8_t *) &tmp;
uint8_t *p = (uint8_t *) &n;
t[0] = p[3];
t[1] = p[2];
t[2] = p[1];
t[3] = p[0];
return tmp;
}