57 Commits

Author SHA1 Message Date
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
a8e2a9349e Exit with appropriate return code if password is not provided, fixes #87 2024-11-07 21:39:37 +02:00
deed43ad61 Makefile macOS fix, resolves #82, resolves #102
- Remove "ginstall" as "install" replacement on macOS
- Remove "-D" flag from "install" parameters
2024-11-07 17:14:25 +02:00
aa933d2c1f Add support for Valve style authentication, fixes #106 2024-11-07 14:59:31 +02:00
accae57e4b Remove ".travis.yml" 2024-11-07 14:21:31 +02:00
fa25cde79c Modify compilation flags:
- Remove "-s" flag
- Change stack protector mode from "strong" to "all"
- Change optimization level from "-Os" to "-O2"
2024-11-07 14:06:48 +02:00
e96b2eff6e Remove broken Travis CI links and update package list 2024-11-07 13:54:40 +02:00
b5951e9634 Merge pull request #83 from Tiiffi/develop
Merge develop to master
2021-10-30 22:34:42 +03:00
7b8ea2bf39 Update copyright year 2021-10-30 22:30:12 +03:00
05aaff88d4 Update version information, changelog and readme 2021-10-30 22:21:25 +03:00
fca278e092 - Quit gracefully when Ctrl-D or Ctrl-C is pressed
- Remove "exit" and "quit" as quit commands
2021-10-30 22:16:29 +03:00
b3147ebe43 Fix erroneous string length check 2021-02-15 04:31:07 +02:00
48c065c304 Use setvbuf() instead of fflush() 2021-02-15 03:29:44 +02:00
29a1c99f82 Merge pull request #53 from AddisonG/master
Fix compiler warning
2021-02-12 04:23:54 +02:00
bf11460a0d Merge pull request #39 from kabiroberai/master
Flush stdout when needed
2021-02-08 22:51:54 +02:00
3e8acd5e42 Merge pull request #62 from jbaldus/master
Fixes typo in ANSI escape sequence for LCYAN
2021-02-08 05:16:11 +02:00
ada14bb4d9 Fixes typo in ANSI escape sequence for LCYAN 2021-01-13 19:22:02 -05:00
336f528668 Fixed compiler bug
This fixes the compiler issue:

```
mcrcon.c: In function ‘packet_build’:
mcrcon.c:576:2: warning: ‘strncpy’ specified bound 4096 equals destination size [-Wstringop-truncation]
  strncpy(packet.data, s1, DATA_BUFFSIZE);
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
2020-08-12 14:10:58 +10:00
58a7a86bc3 Flush stdout when needed
This is required on certain machines (eg the iSH emulator on iOS)
2020-01-31 17:26:27 +05:30
b02201d689 Merge pull request #37 from Tiiffi/develop
Update CHANGELOG.md
2020-01-02 03:14:12 +02:00
4cff2fe537 Update CHANGELOG.md 2020-01-02 03:11:50 +02:00
0d11c6a4e9 Update README.md 2020-01-02 02:40:05 +02:00
9c68d9cdd4 Update README.md 2020-01-02 02:30:12 +02:00
6d49968df3 Update README.md 2020-01-02 02:28:52 +02:00
b118b7b7b9 Change year in copyright texts and manual page 2020-01-02 02:22:44 +02:00
253fb70a72 Merge branch 'develop' 2020-01-02 02:15:27 +02:00
f351fb134e - Deprecate -i flag for terminal mode
- Use strcasecmp() instead strcmp() to check exit command
2019-12-22 16:27:36 +02:00
af5e88b528 Update Makefile 2019-12-22 00:05:31 +02:00
83cdaae362 Update version number 2019-12-21 23:13:19 +02:00
876201fe5a Add workaround to prevent server-side bug.
https://bugs.mojang.com/browse/MC-154617
2019-12-21 23:12:39 +02:00
3c071c0c45 - Add exit string detection
- Stylistic change in getopt loop
2019-12-21 22:25:08 +02:00
d2ebbd8f12 Update various document files 2019-12-21 22:16:34 +02:00
cffa73d223 Remove unused functions 2019-12-20 21:02:21 +02:00
c62d5c8a70 Update usage and version texts 2019-12-20 20:58:40 +02:00
8b75ddf4bf Indentation style change 2019-12-20 19:56:05 +02:00
4f3a455095 Add more validation to mcrcon_parse_seconds() 2019-12-19 17:34:19 +02:00
5fb20c2b83 Update mcrcon.1 and README.md 2019-12-19 17:32:47 +02:00
0c48fbb847 Remove unused option characters 2019-12-18 19:34:08 +02:00
fcc9d6d09b Tabs are evil 2019-12-18 19:19:30 +02:00
018592abc5 Code cleanups 2019-12-18 19:16:06 +02:00
de926b8c26 Merge branch 'develop' of https://github.com/Tiiffi/mcrcon into develop 2019-12-18 19:08:21 +02:00
0f796b6bbc Tidy code formatting 2019-12-18 19:06:37 +02:00
f9d9a02348 Update README.md 2019-12-18 18:29:31 +02:00
a8863e1fcc Merge branch 'develop' of https://github.com/Tiiffi/mcrcon into develop 2019-12-18 18:19:50 +02:00
83457075b9 Make command throttling argument parsing more robust 2019-12-18 18:19:08 +02:00
2f1f925ac1 Make command throttling more robust 2019-12-18 18:02:20 +02:00
8ff710e231 Update README.md 2019-12-18 18:00:45 +02:00
7737bc2a32 Update mcrcon.1 2019-12-18 18:00:31 +02:00
52e717a92b Update CHANGELOG.md 2019-12-18 18:00:11 +02:00
8fdda295e2 Update mcrcon.1 2019-11-19 09:47:05 +02:00
0b1853dccc Update INSTALL.md 2019-11-19 09:17:20 +02:00
d338537e23 Update README.md 2019-11-19 09:16:11 +02:00
10 changed files with 373 additions and 308 deletions

View File

@ -1,20 +0,0 @@
language: c
sudo: false
dist: trusty
cache:
ccache: true
compiler:
- gcc
- clang
env:
global:
- EXTRAFLAGS='-v'
- PREFIX="${HOME}"
install:
- 'true'
script:
- make
- ./mcrcon -h
- ./mcrcon -v
- make install
- make uninstall

View File

@ -1,5 +1,27 @@
#### Version history:
###### 0.7.3
- Add support to Valve style rcon authentication
- Change maximum packet size to correct value (4096 -> 4106)
###### 0.7.2
- Quit gracefully when Ctrl-D or Ctrl+C is pressed
- Remove "exit" and "quit" as quitting commands
* these are actual rcon commands on some servers
- Suppress compiler warning (strncpy)
- Fix erroneous string length in packet building function
- Fix typo in ANSI escape sequence for LCYAN
- Make stdout and stderr unbuffered
###### 0.7.1
- Deprecate `-i` flag for invoking terminal mode
- 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

View File

@ -1,15 +1,15 @@
Compiling and installing
------------------------
Building and installing
-----------------------
Only dependency is C library and POSIX getopt support.
Compiling with GCC or CLANG:
cc -std=gnu99 -Wpedantic -Wall -Wextra -Os -s -o mcrcon mcrcon.c
cc -std=gnu99 -Wpedantic -Wall -Wextra -O2 -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 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/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-2019, Tiiffi <tiiffi at gmail>
Copyright (c) 2012-2024, Tiiffi <tiiffi at gmail>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@ -18,4 +18,3 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source
distribution.

View File

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

View File

@ -4,30 +4,36 @@ mcrcon is console based Minecraft [rcon](https://developer.valvesoftware.com/wik
---
### Installing:
### Installing
##### via packet manager:
See https://pkgs.org/download/mcrcon for available packages in various Linux distros.
##### Binary releases
- Gentoo Linux: https://packages.gentoo.org/packages/games-util/mcrcon
- Arch Linux: https://aur.archlinux.org/packages/mcrcon/
Pre-built binaries are provided for Linux and Windows: https://github.com/Tiiffi/mcrcon/releases/latest
##### from sources:
##### 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).
- Fedora: https://packages.fedoraproject.org/pkgs/mcrcon/mcrcon/
- Gentoo: https://packages.gentoo.org/packages/games-util/mcrcon
- Arch: https://aur.archlinux.org/packages/mcrcon/
- NixOS: https://search.nixos.org/packages?show=mcrcon
- Snapcraft: https://snapcraft.io/mcrcon-nsg
- Scoop: https://scoop.sh/#/apps?q=mcrcon
##### Building from sources
```sh
git clone https://github.com/Tiiffi/mcrcon.git
cd mcrcon
make
# install is optional
sudo make install
```
Check [INSTALL.md](INSTALL.md) for more details.
You can also download precompiled binaries*: https://github.com/Tiiffi/mcrcon/releases/latest
<sub>*At the moment binaries are provided for Linux and Windows.</sub>
---
### Usage:
### Usage
mcrcon [OPTIONS] [COMMANDS]
Sends rcon commands to Minecraft server.
@ -41,9 +47,9 @@ Option:
-s Silent mode
-c Disable colors
-r Output raw packets
-w Wait for specified duration (seconds) between each command (1 - 600s)
-h Print usage
-v Version information
-w Wait for specified duration (seconds) between each command
```
Server address, port and password can be set using following environment variables:
```
@ -56,13 +62,18 @@ MCRCON_PASS
- Command-line options will override environment variables
- Rcon commands with spaces must be enclosed in quotes
Example:
```mcrcon -H my.minecraft.server -p password "say Server is restarting!" save-all stop```
###### Example:
Send three commands ("say", "save-all", "stop") and wait five seconds between the commands:
```sh
mcrcon -H my.minecraft.server -p password -w 5 "say Server is restarting!" save-all stop
```
---
##### Enable rcon on server
Remember to enable rcon by adding following lines to [```server.properties```](https://minecraft.gamepedia.com/Server.properties) file.
##### How to enable rcon on a Minecraft Server
Enable rcon by adding following lines to [```server.properties```](https://minecraft.gamepedia.com/Server.properties) configuration file.
```
enable-rcon=true
rcon.port=25575
@ -71,20 +82,25 @@ rcon.password=your_rcon_pasword
---
##### Contact:
### Contact
* WWW: https://github.com/Tiiffi/mcrcon/
* MAIL: tiiffi at gmail
* IRC: tiiffi @ quakenet
* BUG REPORTS: https://github.com/Tiiffi/mcrcon/issues/
* MAIL: tiiffi+mcrcon at gmail
* ISSUES: https://github.com/Tiiffi/mcrcon/issues/
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
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>Develop:</sub> ![Develop build](https://api.travis-ci.org/Tiiffi/mcrcon.svg?branch=develop)

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

@ -1,7 +1,7 @@
.\" Process this file with
.\" groff -man -Tascii mcrcon.1
.\"
.TH MCRCON 1 "October 2019" "Version 0.6.2"
.TH MCRCON 1 "November 2024" "Version 0.7.3"
.SH NAME
mcrcon \- send rcon commands to a Minecraft server
.SH SYNOPSIS
@ -27,17 +27,17 @@ Silent mode
Disable colors
.IP -r
Output raw packets
.IP -w
Wait for specified duration (seconds) between each command (1 - 600s)
.IP -h
Print usage
.IP -v
Output version information
.IP -w
Wait for specified duration (seconds) between each command
.PP
Commands with spaces must be enclosed in quotes.
.br
mcrcon will start in terminal mode if no commands are given.
.SH ENVIRONMENTAL VARIABLES
.SH ENVIRONMENT VARIABLES
Server address, port and password can be set with following environment variables:
.PP
\fBMCRCON_HOST
@ -58,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"
.RE
.PP
Send three commands to server ("say", "save-all" and "stop"), and wait 2 seconds between them
Send three commands ("say", "save-all", "stop") and wait five seconds between the commands.
.RS
\fBmcrcon\fR -H my.minecraft.server -p password -w 2 "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
.SH BUGS
Bugs can be reported to \fBtiiffi+mcrcon at gmail\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

458
mcrcon.c
View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2019, Tiiffi <tiiffi at gmail>
* Copyright (c) 2012-2021, Tiiffi <tiiffi at gmail>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
@ -24,11 +24,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#ifdef _WIN32
// for name resolving on windows
@ -46,7 +47,7 @@
#include <netdb.h>
#endif
#define VERSION "0.6.2"
#define VERSION "0.7.3"
#define IN_NAME "mcrcon"
#define VER_STR IN_NAME" "VERSION" (built: "__DATE__" "__TIME__")"
@ -56,51 +57,49 @@
#define RCON_AUTH_RESPONSE 2
#define RCON_PID 0xBADC0DE
// a bit too big perhaps?
#define DATA_BUFFSIZE 4096
#define DATA_BUFFSIZE 4096
#define MAX_PACKET_SIZE 4106
#define MIN_PACKET_SIZE 10
// rcon packet structure
typedef struct _rc_packet {
int size;
int id;
int cmd;
int32_t size;
int32_t id;
int32_t cmd;
char data[DATA_BUFFSIZE];
// ignoring string2 atm.
// ignoring string2 for now
} rc_packet;
// ===================================
// FUNCTION DEFINITIONS
// ===================================
// endianness related functions
bool is_bigendian(void);
int32_t reverse_int32(int32_t n);
// Network related functions
#ifdef _WIN32
void net_init_WSA(void);
void net_init_WSA(void);
#endif
void net_close(int sd);
int net_connect(const char *host, const char *port);
int net_send(int sd, const uint8_t *buffer, size_t size);
int net_send_packet(int sd, rc_packet *packet);
rc_packet* net_recv_packet(int sd);
int net_clean_incoming(int sd, int size);
void net_close(int sd);
int net_connect(const char *host, const char *port);
int net_send(int sd, const uint8_t *buffer, size_t size);
int net_send_packet(int sd, rc_packet *packet);
rc_packet* net_recv_packet(int sd);
int net_clean_incoming(int sd, int size);
// Misc stuff
void usage(void);
void usage(void);
#ifndef _WIN32
void print_color(int color);
void print_color(int color);
#endif
int get_line(char *buffer, int len);
int run_terminal_mode(int sock);
int run_commands(int argc, char *argv[]);
int get_line(char *buffer, int len);
int run_terminal_mode(int sock);
int run_commands(int argc, char *argv[]);
// Rcon protocol related functions
rc_packet* packet_build(int id, int cmd, char *s1);
void packet_print(rc_packet *packet);
int rcon_auth(int sock, char *passwd);
int rcon_command(int sock, char *command);
rc_packet* packet_build(int id, int cmd, char *s1);
void packet_print(rc_packet *packet);
int rcon_auth(int sock, char *passwd);
int rcon_command(int sock, char *command);
// =============================================
@ -108,10 +107,10 @@ int rcon_command(int sock, char *command);
// =============================================
static int global_raw_output = 0;
static int global_silent_mode = 0;
static int global_print_colors = 1;
static int global_disable_colors = 0;
static int global_connection_alive = 1;
static int global_wait_seconds = 0;
static int global_rsock;
static int global_wait_seconds = 0;
#ifdef _WIN32
// console coloring on windows
@ -126,85 +125,98 @@ void exit_proc(void)
}
// Check windows & linux behaviour !!!
void sighandler(/*int sig*/)
void sighandler(int sig)
{
if (sig == SIGINT)
putchar('\n');
global_connection_alive = 0;
#ifndef _WIN32
exit(EXIT_SUCCESS);
#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 opt;
int terminal_mode = 0;
char *host = getenv("MCRCON_HOST");
char *pass = getenv("MCRCON_PASS");
char *port = getenv("MCRCON_PORT");
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
opterr = 1;
while ((opt = getopt(argc, argv, "vrtcshw:H:p:P:i")) != -1)
int opt;
while ((opt = getopt(argc, argv, "vrtcshw:H:p:P:")) != -1)
{
switch (opt)
{
case 'H': host = optarg; break;
case 'P': port = optarg; break;
case 'p': pass = optarg; break;
case 'C':
case 'c': global_print_colors = 0; break;
case 'S':
case 's': global_silent_mode = 1; break;
case 'T':
case 't':
case 'I':
case 'i': terminal_mode = 1; break;
case 'r': global_raw_output = 1; break;
case 'v':
puts(VER_STR"\nhttps://github.com/Tiiffi/mcrcon");
exit(EXIT_SUCCESS);
case 'W':
switch (opt) {
case 'H': host = optarg; break;
case 'P': port = optarg; break;
case 'p': pass = optarg; break;
case 'c': global_disable_colors = 1; break;
case 's': global_silent_mode = 1; break;
case 'i': /* reserved for interp mode */break;
case 't': terminal_mode = 1; break;
case 'r': global_raw_output = 1; break;
case 'w':
global_wait_seconds = strtol(optarg, NULL, 10);
if (errno != 0)
{
fprintf(stderr, "Error %d: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
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);
*/
global_wait_seconds = mcrcon_parse_seconds(optarg);
break;
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 (pass == NULL)
{
fputs("You must give password (-p password). Try 'mcrcon -h' or 'man mcrcon' for help.\n\n", stdout);
return 0;
if (pass == NULL) {
puts("You must give password (-p password).\nTry 'mcrcon -h' or 'man mcrcon' for help.");
exit(EXIT_FAILURE);
}
if(optind == argc && terminal_mode == 0)
terminal_mode = 1;
// safety features to prevent "IO: Connection reset" bug on the server side
atexit(&exit_proc);
signal(SIGABRT, &sighandler);
@ -212,9 +224,10 @@ int main(int argc, char *argv[])
signal(SIGINT, &sighandler);
#ifdef _WIN32
net_init_WSA();
console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle == INVALID_HANDLE_VALUE) console_handle = NULL;
net_init_WSA();
console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle == INVALID_HANDLE_VALUE)
console_handle = NULL;
#endif
// open socket
@ -223,28 +236,23 @@ int main(int argc, char *argv[])
int exit_code = EXIT_SUCCESS;
// auth & commands
if (rcon_auth(global_rsock, pass))
{
if (rcon_auth(global_rsock, pass)) {
if (terminal_mode)
run_terminal_mode(global_rsock);
else
exit_code = run_commands(argc, argv);
}
else // auth failed
{
else { // auth failed
fprintf(stdout, "Authentication failed!\n");
exit_code = EXIT_FAILURE;
}
net_close(global_rsock);
global_rsock = -1;
return exit_code;
exit(exit_code);
}
void usage(void)
{
fputs(
puts(
"Usage: "IN_NAME" [OPTIONS] [COMMANDS]\n\n"
"Send rcon commands to Minecraft server.\n\n"
"Options:\n"
@ -255,21 +263,21 @@ void usage(void)
" -s\t\tSilent mode\n"
" -c\t\tDisable colors\n"
" -r\t\tOutput raw packets\n"
" -w\t\tWait for specified duration (seconds) between each command (1 - 600s)\n"
" -h\t\tPrint usage\n"
" -v\t\tVersion information\n"
" -w\t\tWait for specified duration (seconds) between each command\n\n"
" -v\t\tVersion information\n\n"
"Server address, port and password can be set with following environment variables:\n"
" MCRCON_HOST\n"
" MCRCON_PORT\n"
" MCRCON_PASS\n\n"
,stdout
" MCRCON_PASS\n"
);
puts("mcrcon will start in terminal mode if no commands are given.");
puts("Command-line options will override environment variables.");
puts("Rcon commands with spaces must be enclosed in quotes.\n");
puts("Example:\n\t"IN_NAME" -H my.minecraft.server -p password \"say Server is restarting!\" save-all stop\n");
puts(VER_STR"\nReport bugs to tiiffi+mcrcon at gmail or https://github.com/Tiiffi/mcrcon/issues/\n");
puts (
"- mcrcon will start in terminal mode if no commands are given\n"
"- Command-line options will override environment variables\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
puts("Press enter to exit.");
@ -289,8 +297,7 @@ void net_init_WSA(void)
WORD version = MAKEWORD(2, 2);
int err = WSAStartup(version, &wsadata);
if (err != 0)
{
if (err != 0) {
fprintf(stderr, "WSAStartup failed. Error: %d.\n", err);
exit(EXIT_FAILURE);
}
@ -300,12 +307,12 @@ void net_init_WSA(void)
// socket close and cleanup
void net_close(int sd)
{
#ifdef _WIN32
closesocket(sd);
WSACleanup();
#else
close(sd);
#endif
#ifdef _WIN32
closesocket(sd);
WSACleanup();
#else
close(sd);
#endif
}
// Opens and connects socket
@ -328,27 +335,26 @@ int net_connect(const char *host, const char *port)
#endif
int ret = getaddrinfo(host, port, &hints, &server_info);
if (ret != 0)
{
if (ret != 0) {
fprintf(stderr, "Name resolution failed.\n");
#ifdef _WIN32
fprintf(stderr, "Error %d: %s", ret, gai_strerror(ret));
#else
fprintf(stderr, "Error %d: %s\n", ret, gai_strerror(ret));
#endif
exit(EXIT_FAILURE);
}
// 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);
if (sd == -1)
continue;
ret = connect(sd, p->ai_addr, p->ai_addrlen);
if (ret == -1)
{
if (ret == -1) {
net_close(sd);
continue;
}
@ -356,19 +362,18 @@ int net_connect(const char *host, const char *port)
break;
}
if (p == NULL)
{
if (p == NULL) {
/* TODO (Tiiffi): Check why windows does not report errors */
fprintf(stderr, "Connection failed.\n");
#ifndef _WIN32
fprintf(stderr, "Error %d: %s\n", errno, strerror(errno));
#endif
freeaddrinfo(server_info);
exit(EXIT_FAILURE);
}
freeaddrinfo(server_info);
return sd;
}
@ -377,11 +382,11 @@ int net_send(int sd, const uint8_t *buff, size_t size)
size_t sent = 0;
size_t left = size;
while (sent < size)
{
while (sent < size) {
int result = send(sd, (const char *) buff + sent, left, 0);
if (result == -1) return -1;
if (result == -1)
return -1;
sent += result;
left -= sent;
@ -397,12 +402,11 @@ int net_send_packet(int sd, rc_packet *packet)
int bytesleft; // bytes left to send
int ret = -1;
bytesleft = len = packet->size + sizeof(int);
bytesleft = len = packet->size + sizeof(int32_t);
while(total < len)
{
while (total < len) {
ret = send(sd, (char *) packet + total, bytesleft, 0);
if(ret == -1) { break; }
if(ret == -1) break;
total += ret;
bytesleft -= ret;
}
@ -412,32 +416,31 @@ int net_send_packet(int sd, rc_packet *packet)
rc_packet *net_recv_packet(int sd)
{
int psize;
int32_t psize;
static rc_packet packet = {0, 0, 0, { 0x00 }};
// packet.size = packet.id = packet.cmd = 0;
int ret = recv(sd, (char *) &psize, sizeof(int), 0);
int ret = recv(sd, (char *) &psize, sizeof(psize), 0);
if (ret == 0)
{
if (ret == 0) {
fprintf(stderr, "Connection lost.\n");
global_connection_alive = 0;
return NULL;
}
if (ret != sizeof(int))
{
if (ret != sizeof(psize)) {
fprintf(stderr, "Error: recv() failed. Invalid packet size (%d).\n", ret);
global_connection_alive = 0;
return NULL;
}
if (psize < 10 || psize > DATA_BUFFSIZE)
{
fprintf(stderr, "Warning: invalid packet size (%d). Must over 10 and less than %d.\n", psize, DATA_BUFFSIZE);
// NOTE(Tiiffi): This should fail if size is out of spec!
if (psize < MIN_PACKET_SIZE || psize > MAX_PACKET_SIZE) {
fprintf(stderr, "Warning: invalid packet size (%d). Must over 10 and less than %d.\n", psize, MAX_PACKET_SIZE);
if(psize > DATA_BUFFSIZE || psize < 0) psize = DATA_BUFFSIZE;
// WARNING(Tiiffi): This is probably not the way to go. Probably should just fail and exit.
if(psize > MAX_PACKET_SIZE || psize < 0) psize = MAX_PACKET_SIZE;
net_clean_incoming(sd, psize);
return NULL;
@ -446,11 +449,9 @@ rc_packet *net_recv_packet(int sd)
packet.size = psize;
int received = 0;
while (received < psize)
{
ret = recv(sd, (char *) &packet + sizeof(int) + received, psize - received, 0);
if (ret == 0) /* connection closed before completing receving */
{
while (received < psize) {
ret = recv(sd, (char *) &packet + sizeof(int32_t) + received, psize - received, 0);
if (ret == 0) {
fprintf(stderr, "Connection lost.\n");
global_connection_alive = 0;
return NULL;
@ -465,11 +466,9 @@ rc_packet *net_recv_packet(int sd)
int net_clean_incoming(int sd, int size)
{
char tmp[size];
int ret = recv(sd, tmp, size, 0);
if(ret == 0)
{
if(ret == 0) {
fprintf(stderr, "Connection lost.\n");
global_connection_alive = 0;
}
@ -481,8 +480,7 @@ void print_color(int color)
{
// sh compatible color array
#ifndef _WIN32
char *colors[] =
{
char *colors[] = {
"\033[0;30m", /* 00 BLACK 0x30 */
"\033[0;34m", /* 01 BLUE 0x31 */
"\033[0;32m", /* 02 GREEN 0x32 */
@ -494,7 +492,7 @@ void print_color(int color)
"\033[0;1;30m", /* 08 DGREY 0x38 */
"\033[0;1;34m", /* 09 LBLUE 0x39 */
"\033[0;1;32m", /* 10 LGREEN 0x61 */
"\033[0:1;36m", /* 11 LCYAN 0x62 */
"\033[0;1;36m", /* 11 LCYAN 0x62 */
"\033[0;1;31m", /* 12 LRED 0x63 */
"\033[0;1;35m", /* 13 LPURPLE 0x64 */
"\033[0;1;33m", /* 14 YELLOW 0x65 */
@ -502,22 +500,22 @@ void print_color(int color)
"\033[4m" /* 16 UNDERLINE 0x6e */
};
if(color == 0 || color == 0x72) /* 0x72: 'r' */
{
fputs("\033[0m", stdout); /* CANCEL COLOR */
}
/* 0x72: 'r' */
if (color == 0 || color == 0x72) fputs("\033[0m", stdout); /* CANCEL COLOR */
else
#endif
{
if(color >= 0x61 && color <= 0x66) color -= 0x57;
else if(color >= 0x30 && color <= 0x39) color -= 0x30;
else if(color == 0x6e) color=16; /* 0x6e: 'n' */
if (color >= 0x61 && color <= 0x66) color -= 0x57;
else if (color >= 0x30 && color <= 0x39)
color -= 0x30;
else if (color == 0x6e)
color = 16; /* 0x6e: 'n' */
else return;
#ifndef _WIN32
fputs(colors[color], stdout);
fputs(colors[color], stdout);
#else
SetConsoleTextAttribute(console_handle, color);
SetConsoleTextAttribute(console_handle, color);
#endif
}
}
@ -525,9 +523,10 @@ void print_color(int color)
// this hacky mess might use some optimizing
void packet_print(rc_packet *packet)
{
if (global_raw_output == 1)
{
for (int i = 0; packet->data[i] != 0; ++i) putchar(packet->data[i]);
if (global_raw_output == 1) {
for (int i = 0; packet->data[i] != 0; ++i)
putchar(packet->data[i]);
return;
}
@ -535,21 +534,17 @@ void packet_print(rc_packet *packet)
int def_color = 0;
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO console_info;
if (GetConsoleScreenBufferInfo(console_handle, &console_info) != 0)
def_color = console_info.wAttributes + 0x30;
else
def_color = 0x37;
CONSOLE_SCREEN_BUFFER_INFO console_info;
if (GetConsoleScreenBufferInfo(console_handle, &console_info) != 0) {
def_color = console_info.wAttributes + 0x30;
} else def_color = 0x37;
#endif
// colors enabled so try to handle the bukkit colors for terminal
if (global_print_colors == 1)
{
for (i = 0; (unsigned char) packet->data[i] != 0; ++i)
{
if (global_disable_colors == 0) {
for (i = 0; (unsigned char) packet->data[i] != 0; ++i) {
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]);
continue;
}
@ -558,11 +553,9 @@ void packet_print(rc_packet *packet)
print_color(def_color); // cancel coloring
}
// strip colors
else
{
for (i = 0; (unsigned char) packet->data[i] != 0; ++i)
{
if ((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7){
else {
for (i = 0; (unsigned char) packet->data[i] != 0; ++i) {
if ((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) {
i+=2;
continue;
}
@ -571,26 +564,29 @@ void packet_print(rc_packet *packet)
}
// print newline if string has no newline
if (packet->data[i-1] != 10 && packet->data[i-1] != 13)
putchar('\n');
if (packet->data[i-1] != 10 && packet->data[i-1] != 13) putchar('\n');
}
rc_packet *packet_build(int id, int cmd, char *s1)
{
static rc_packet packet = {0, 0, 0, { 0x00 }};
// NOTE(Tiiffi): Issue report states that outgoing payload has max size of 1460 bytes:
// https://github.com/Tiiffi/mcrcon/issues/45#issuecomment-1000940814
// https://mctools.readthedocs.io/en/master/rcon.html
// Have to do some testing to confirm!
// size + id + cmd + s1 + s2 NULL terminator
int s1_len = strlen(s1);
if (s1_len > DATA_BUFFSIZE)
{
fprintf(stderr, "Warning: Command string too long (%d). Maximum allowed: %d.\n", s1_len, DATA_BUFFSIZE);
int len = strlen(s1);
if (len >= DATA_BUFFSIZE) {
fprintf(stderr, "Warning: Command string too long (%d). Maximum allowed: %d.\n", len, DATA_BUFFSIZE - 1);
return NULL;
}
packet.size = sizeof(int) * 2 + s1_len + 2;
packet.size = sizeof packet.size * 2 + len + 2;
packet.id = id;
packet.cmd = cmd;
strncpy(packet.data, s1, DATA_BUFFSIZE);
strncpy(packet.data, s1, DATA_BUFFSIZE - 1);
return &packet;
}
@ -607,10 +603,18 @@ int rcon_auth(int sock, char *passwd)
if (!ret)
return 0; // send failed
receive:
packet = net_recv_packet(sock);
if (packet == NULL)
return 0;
/* Valve rcon sends empty "RCON_RESPONSEVALUE" packet before real auth response
* so we have to check packet type and try again if necessary.
*/
if (packet->cmd != RCON_AUTH_RESPONSE) {
goto receive;
}
// return 1 if authentication OK
return packet->id == -1 ? 0 : 1;
}
@ -618,8 +622,7 @@ int rcon_auth(int sock, char *passwd)
int rcon_command(int sock, char *command)
{
rc_packet *packet = packet_build(RCON_PID, RCON_EXEC_COMMAND, command);
if (packet == NULL)
{
if (packet == NULL) {
global_connection_alive = 0;
return 0;
}
@ -633,16 +636,9 @@ int rcon_command(int sock, char *command)
if (packet->id != RCON_PID)
return 0;
if (!global_silent_mode)
{
/*
if(packet->size == 10) {
printf("Unknown command \"%s\". Type \"help\" or \"?\" for help.\n", command);
}
else
*/
if (packet->size > 10)
packet_print(packet);
if (!global_silent_mode) {
if (packet->size > 10)
packet_print(packet);
}
return 1;
@ -652,16 +648,14 @@ int run_commands(int argc, char *argv[])
{
int i = optind;
for (;;)
{
for (;;) {
if (!rcon_command(global_rsock, argv[i]))
return EXIT_FAILURE;
if (++i >= argc)
return EXIT_SUCCESS;
if (global_wait_seconds > 0)
{
if (global_wait_seconds > 0) {
#ifdef _WIN32
Sleep(global_wait_seconds * 1000);
#else
@ -675,20 +669,34 @@ int run_commands(int argc, char *argv[])
int run_terminal_mode(int sock)
{
int ret = 0;
char command[DATA_BUFFSIZE] = {0x00};
char command[DATA_BUFFSIZE] = {0};
puts("Logged in. Type \"Q\" to quit!");
puts("Logged in.\nType 'Q' or press Ctrl-D / Ctrl-C to disconnect.");
while (global_connection_alive) {
putchar('>');
while (global_connection_alive)
{
int len = get_line(command, DATA_BUFFSIZE);
if(command[0] == 'Q' && command[1] == 0)
if (len < 1) continue;
if (strcasecmp(command, "Q") == 0)
break;
if(len > 0 && global_connection_alive)
if (len > 0 && global_connection_alive)
ret += rcon_command(sock, command);
command[0] = len = 0;
/* 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;
}
return ret;
@ -697,44 +705,26 @@ int run_terminal_mode(int sock)
// gets line from stdin and deals with rubbish left in the input buffer
int get_line(char *buffer, int bsize)
{
int ch, len;
fputs(">", stdout);
char *ret = fgets(buffer, bsize, stdin);
if (ret == NULL) exit(EXIT_FAILURE);
if (buffer[0] == 0) global_connection_alive = 0;
if (ret == NULL) {
if (ferror(stdin)) {
fprintf(stderr, "Error %d: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
putchar('\n');
exit(EXIT_SUCCESS);
}
// remove unwanted characters from the buffer
buffer[strcspn(buffer, "\r\n")] = '\0';
len = strlen(buffer);
int len = strlen(buffer);
// clean input buffer if needed
if (len == bsize - 1)
if (len == bsize - 1) {
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}
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;
}