diff --git a/.gitignore b/.gitignore index 0a05717..190da4c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.o *.dev *.win +*.txt *.layout *.project mcrcon +todo diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..67c1e9b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,64 @@ +####Version history: +######0.0.5 + - IPv6 support! + * Thanks to 'Tanja84dk' for addressing the real need of IPv6. + + - Fixed bug causing crash / segmentation fault (invalid write) when receiving malformed rcon packet. + + - Program makes use of C99 feature (variable-length arrays) so "-std=gnu99" flag on + GCC-compiler must be used to avoid unecessary warnings. + + - Rcon receive buffer is now bigger (2024 bytes -> 10240 bytes). + * Thanks to 'gman_ftw' @ Bukkit forums. + + - Fixed invalid error message when receiving empty rcon packet (10 bytes). + * Thanks to 'pkmnfrk' @ bukkit forums. + + - Terminal mode now closes automatically when rcon socket is closed by server + or if packet size cannot be retrieved correctly. + + - Client now tries to clean the incoming socket data if last package was out of spec. + +######0.0.4 + - Reverted back to default getopts options error handler (opterr = 1). + Custom error handler requires rewriting. + - Some cosmetic changes in program output strings. + - Program usage(); function now waits for enter before exiting on Windows. + +######0.0.3 + - Colors are now supported on Windows too! + - Terminal mode is now triggered with "-t" flag. "-i" flag still works for + backwards compatibility. + - Bug fixes (Packet size check always evaluating false and color validity + check always evaluating true). + +######0.0.2 + - License changed from 'ISC License' to 'zlib/libpng License'. + - Bug fixes & code cleanups + - Interactive mode (-i flag). Client acts as interactive terminal. + - Program return value is now the number of rcon commmands sent successfully. + If connecting or authentication fails, the return value is -1. + - Colors are now enabled by default. Now '-c' flag disables the color support. + +######0.0.1 + - Added experimental support for bukkit colors. + Should work with any sh compatible shell. + - Packet string data limited to max 2048 (DATA_BUFFSIZE) bytes. + No idea how Minecraft handles multiple rcon packets. + 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) diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..54cb5b8 --- /dev/null +++ b/INSTALL @@ -0,0 +1,21 @@ +Compiling and installing +------------------------ + +Only dependency is C library with POSIX getopt support. + +Compiling with GCC or CLANG: + + cc -std=gnu99 -Wpedantic -Wall -Wextra -Os -s -o mcrcon mcrcon.c + +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/share/bin/mcrcon + /usr/share/man/man1/mcrcon.1 + +On Window remember to link with winsockets by adding "-lws2_32" to your compiler command line. +Makefile should take care of this automatically but "install" and "uninstall" rules are disabled on windows. diff --git a/LICENSE b/LICENSE index 2dc77ad..a3e4a6e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2015, Tiiffi gmail_dot_com> +Copyright (c) 2012-2016, Tiiffi This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/Makefile b/Makefile index 1c3e5da..b17c7f8 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,36 @@ -# Cross comple, eg. +# if you want to cross compile # export PATH=$PATH:/path/to/compiler/bin # export CROSS_COMPILE=arm-none-linux-gnueabi- # make -all: - $(CROSS_COMPILE)gcc -std=gnu99 -pedantic -Wall -Wextra -O2 -s -o mcrcon mcrcon.c +ifeq ($(OS), Windows_NT) + LINKER = -lws2_32 + EXENAME = mcrcon.exe + RM = cmd /C del /F +else + LINKER = + EXENAME = mcrcon + RM = rm -f +endif +CC = gcc +CFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -Os -s + +all: + $(CROSS_COMPILE)$(CC) $(CFLAGS) -o $(EXENAME) mcrcon.c $(LINKER) + +ifneq ($(OS), Windows_NT) +install: + cp $(EXENAME) /usr/local/bin/$(EXENAME) + chmod 0755 /usr/local/bin/$(EXENAME) + cp mcrcon.1 /usr/local/share/man/man1/mcrcon.1 + chmod 0644 /usr/local/share/man/man1/mcrcon.1 + @echo "\nmcrcon installed. Run 'make uninstall' if you want to uninstall.\n" +uninstall: + rm -f /usr/local/bin/$(EXENAME) + rm -f /usr/local/share/man/man1/mcrcon.1 + @echo "\nmcrcon uninstalled.\n" +endif + +clean: + $(RM) $(EXENAME) diff --git a/README.md b/README.md index dcacfc9..028b78c 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,59 @@ -####Compiling: +###Installing: -Raw command: -```gcc -std=gnu11 -pedantic -Wall -Wextra -O2 -s -o mcrcon mcrcon.c``` +from sources: +``` +git clone https://github.com/Tiiffi/mcrcon.git +cd mcrcon/ +make +sudo make install +``` +Check **INSTALL** for more details. -or just run **make**. +You can also download precompiled binaries: https://github.com/Tiiffi/mcrcon/releases/latest -On windows, remember to link with winsockets. -Add ```-lws2_32``` to compiler command line on Mingw GCC. +Binaries are provided for Linux and Windows. --- -####Usage: -Usage: mcrcon [OPTIONS]... [COMMANDS]... -Sends rcon commands to minecraft server. +###Usage: +mcrcon [OPTIONS]... [COMMANDS]... + +Sends rcon commands to Minecraft server. ``` Option: - -h Prints usage. - -s Silent mode. Do not print data received from rcon. - -t Terminal mode. Acts as interactive terminal. - -p Rcon password. Default: "". - -H Host address or ip. - -P Port. Default: 25575. - -c Do not print colors. Disables bukkit color printing. - -r Print everything in raw mode. - Good for debugging and custom handling of the output. + -h Print usage + -H Server address + -P Port (default is 25575) + -p Rcon password + -t Interactive terminal mode + -s Silent mode (do not print received packets) + -c Disable colors + -r Output raw packets (debugging and custom handling) + -v Output version information ``` -Invidual commands must be separated with spaces. + +Commands with arguments must be enclosed in quotes. Example: - ```mcrcon -c -H 192.168.1.42 -P 9999 -p password cmd1 "cmd2 with spaces"``` + ```mcrcon -H my.minecraft.server -p password "say Server is restarting!" save-all stop``` -#####Enable rcon -Remember to enable rcon by changing/adding these lines to ```server.properties``` file. +--- + +###Enable rcon on server +Remember to enable rcon by adding following lines to ```server.properties``` file. ``` enable-rcon=true +rcon.port=25575 rcon.password=your_rcon_pasword -rcon.port=9999 ``` --- ####Contact: -* WWW: http://sourceforge.net/projects/mcrcon/ +* WWW: https://github.com/Tiiffi/mcrcon/ * MAIL: tiiffi_at_gmail_dot_com * IRC: tiiffi @ quakenet * BUG REPORTS: https://github.com/Tiiffi/mcrcon/issues/ ---- - -####Version history: -######0.0.5 - - IPv6 support! - * Thanks to 'Tanja84dk' for addressing the real need of IPv6. - - - Fixed bug causing crash / segmentation fault (invalid write) when receiving malformed rcon packet. - - - Program makes use of C99 feature (variable-length arrays) so "-std=gnu99" flag on - GCC-compiler must be used to avoid unecessary warnings. - - - Rcon receive buffer is now bigger (2024 bytes -> 10240 bytes). - * Thanks to 'gman_ftw' @ Bukkit forums. - - - Fixed invalid error message when receiving empty rcon packet (10 bytes). - * Thanks to 'pkmnfrk' @ bukkit forums. - - - Terminal mode now closes automatically when rcon socket is closed by server - or if packet size cannot be retrieved correctly. - - - Client now tries to clean the incoming socket data if last package was out of spec. - -######0.0.4 - - Reverted back to default getopts options error handler (opterr = 1). - Custom error handler requires rewriting. - - Some cosmetic changes in program output strings. - - Program usage(); function now waits for enter before exiting on Windows. - -######0.0.3 - - Colors are now supported on Windows too! - - Terminal mode is now triggered with "-t" flag. "-i" flag still works for - backwards compatibility. - - Bug fixes (Packet size check always evaluating false and color validity - check always evaluating true). - -######0.0.2 - - License changed from 'ISC License' to 'zlib/libpng License'. - - Bug fixes & code cleanups - - Interactive mode (-i flag). Client acts as interactive terminal. - - Program return value is now the number of rcon commmands sent successfully. - If connecting or authentication fails, the return value is -1. - - Colors are now enabled by default. Now '-c' flag disables the color support. - -######0.0.1 - - Added experimental support for bukkit colors. - Should work with any sh compatible shell. - - Packet string data limited to max 2048 (DATA_BUFFSIZE) bytes. - No idea how Minecraft handles multiple rcon packets. - 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) diff --git a/mcrcon.1 b/mcrcon.1 new file mode 100644 index 0000000..481f556 --- /dev/null +++ b/mcrcon.1 @@ -0,0 +1,53 @@ +.\" Process this file with +.\" groff -man -Tascii mcrcon.1 +.\" +.TH MCRCON 1 "November 2016" "Version 0.0.6" +.SH NAME +mcrcon \- sends rcon commands to a Minecraft server +.SH SYNOPSIS +.B mcrcon [ +options +.B ] [ +commands +.B ] +.SH DESCRIPTION +mcrcon is Minecraft rcon client / terminal with bukkit coloring support.\n\n\n +It is well suited for remote administration and server maintenance scripts. +.SH OPTIONS +.IP -h +Print usage +.IP -H +Server address +.IP -P +Port (default is 25575) +.IP -p +Rcon password +.IP -t +Interactive terminal mode +.IP -s +Silent mode (do not print received packets) +.IP -c +Disable colors +.IP -r +Output raw packets (for debugging and custom handling) +.IP -v +Output version information +.PP +Commands with arguments must be enclosed in quotes. +.SH EXAMPLES +Make rcon connection in terminal mode using default port +.RS +\fBmcrcon\fR -H my.minecraft.server -p password +.RE +.PP +Send "weather clear" command to server using custom port 1337 +.RS +\fBmcrcon\fR -H my.minecraft.server -P 1337 -p password "weather clear" +.RE +.PP +Send three commands to server (say, save-all and stop) +.RS +\fBmcrcon\fR -H my.minecraft.server -p password "say Server is restarting!" save-all stop +.RE +.SH BUGS +Bugs can be reported to \fBtiiffi_at_gmail_dot_com\fR or \fBhttps://github.com/Tiiffi/mcrcon/issues/\fR diff --git a/mcrcon.c b/mcrcon.c index a105297..ca0a625 100644 --- a/mcrcon.c +++ b/mcrcon.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015, Tiiffi gmail_dot_com> + * Copyright (c) 2012-2016, Tiiffi gmail_dot_com> * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -23,13 +23,17 @@ #include #include +#include +#include #include -#include #include +#include +#include #ifdef _WIN32 - /* for name resolving on windows */ - #define _WIN32_WINNT 0x0501 + // for name resolving on windows + // enable this if you get compiler whine about getaddrinfo on windows + //#define _WIN32_WINNT 0x0501 #include #include @@ -42,582 +46,743 @@ #include #endif -/* absolute value macro -#define absolute(x) (x < 0) ? (0 - x) : x -*/ +#define VERSION "0.0.6" +#define IN_NAME "mcrcon" +#define VER_STR IN_NAME" "VERSION" (built: "__DATE__" "__TIME__")" #define RCON_EXEC_COMMAND 2 #define RCON_AUTHENTICATE 3 #define RCON_RESPONSEVALUE 0 #define RCON_AUTH_RESPONSE 2 -#define RCON_PID 42 +#define RCON_PID 0xBADC0DE /* Safe value I think. This should me made dynamic for more stable performance! */ #define DATA_BUFFSIZE 10240 -#define VERSION "0.0.5" -#define IN_NAME "mcrcon" -#define VER_STR IN_NAME" "VERSION - -/* rcon packet structure */ +// rcon packet structure typedef struct _rc_packet { int size; int id; int cmd; char data[DATA_BUFFSIZE]; - /* ignoring string2 atm.. */ + // ignoring string2 atm. } rc_packet; -/* functions */ -void usage(void); -void error(char *errstring); +/* =================================== */ +/* 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); +#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); + +// Misc stuff +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 rsock); +int run_commands(int argc, char *argv[]); -struct addrinfo *net_resolve(char *host, char *port); -void net_close_socket(int sd); -int net_open_socket(char *host, char *port); -int net_send_packet(int sd, rc_packet *packet); -rc_packet* net_recv_packet(int sd); -#ifdef _WIN32 -void net_init_WSA(void); -#endif -int net_clean_incoming(int sd, int size); +// Rcon protocol related functions +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); -rc_packet* packet_build(int id, int cmd, char *s1); -void packet_print(rc_packet *packet); - -int rcon_auth(int rsock, char *passwd); -int rcon_command(int rsock, char *command); - -int get_line(char *buffer, int len); -int run_terminal_mode(int rsock); -int run_commands(int argc, char *argv[]); +int rcon_auth(int rsock, char *passwd); +int rcon_command(int rsock, char *command); -/* some globals */ -int raw_output = 0; -int silent_mode = 0; -int print_colors = 1; -int connection_alive = 1; -int rsock; /* rcon socket */ +// ============================================= +// GLOBAL VARIABLES +// ============================================= +static int raw_output = 0; +static int silent_mode = 0; +static int print_colors = 1; +static int connection_alive = 1; +static int rsock; #ifdef _WIN32 - /* console coloring on windows */ + // console coloring on windows HANDLE console_handle; #endif -/* safety stuff (windows is still misbehaving) */ -void exit_proc(void) { - if(rsock != -1) net_close_socket(rsock); +// safety stuff (windows is still misbehaving) +void exit_proc(void) +{ + if (rsock != -1) + net_close(rsock); } -/* Check windows & linux behaviour !!! */ -void sighandler(/*int sig*/) { - connection_alive = 0; - #ifndef _WIN32 - exit(-1); - #endif +// Check windows & linux behaviour !!! +void sighandler(/*int sig*/) +{ + connection_alive = 0; + #ifndef _WIN32 + exit(-1); + #endif } int main(int argc, char *argv[]) { - int opt, ret = 0; - int terminal_mode = 0; + int opt, ret = 0; + int terminal_mode = 0; - char *host = NULL; - char *pass = ""; - char *port = "25575"; + char *host = NULL; + char *pass = NULL; + char *port = "25575"; - if(argc < 2) usage(); + if(argc < 2) + usage(); - opterr = 1; /* default error handler enabled */ - while((opt = getopt(argc, argv, "rtcshH:p:P:i")) != -1) - { - switch(opt) - { - case 'H': host = optarg; break; - case 'P': port = optarg; break; - case 'p': pass = optarg; break; - case 'C': - case 'c': print_colors = 0; break; - case 'S': - case 's': silent_mode = 1; break; - case 'T': - case 't': - case 'I': - case 'i': terminal_mode = 1; break; - case 'r': raw_output = 1; break; - case 'h': - case '?': - /* - if(optopt == 'P' || optopt == 'H' || optopt == 'p') - fprintf (stderr, "Option -%c requires an argument.\n\n", optopt); - */ + // default getopt error handler enabled + opterr = 1; - /* else fprintf (stderr, "Unknown option -%c\n\n", optopt); */ + while ((opt = getopt(argc, argv, "vrtcshH:p:P:i")) != -1) + { + switch (opt) + { + case 'H': host = optarg; break; + case 'P': port = optarg; break; + case 'p': pass = optarg; break; + case 'C': + case 'c': print_colors = 0; break; + case 'S': + case 's': silent_mode = 1; break; + case 'T': + case 't': + case 'I': + case 'i': terminal_mode = 1; break; + case 'r': raw_output = 1; break; + case 'v': + puts(VER_STR"\nhttps://github.com/Tiiffi/mcrcon"); + exit(0); + 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); + */ - usage(); - break; + default: exit(-1); + } + } - default: abort(); - } - } + if (host == NULL) + { + fputs("Host not defined (-H flag). Try 'mcrcon -h' or 'man mcrcon' for more information.\n\n", stdout); + return 0; + } - if(host == NULL) { - fputs("Host not defined. Check -H flag.\n\n", stdout); - usage(); - } + if (pass == NULL) + { + fputs("Password not defined (-p flag). Try 'mcrcon -h' 'man mcrcon' for more information.\n\n", stdout); + return 0; + } - if(optind == argc && terminal_mode == 0) { - fputs("No commands specified.\n\n", stdout); - usage(); - } + 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); - signal(SIGTERM, &sighandler); - signal(SIGINT, &sighandler); - #ifdef _WIN32 - net_init_WSA(); - console_handle = GetStdHandle(STD_OUTPUT_HANDLE); - if(console_handle == INVALID_HANDLE_VALUE) console_handle = NULL; - #endif + // safety features to prevent "IO: Connection reset" bug on the server side + atexit(&exit_proc); + signal(SIGABRT, &sighandler); + signal(SIGTERM, &sighandler); + signal(SIGINT, &sighandler); - /* open socket */ - rsock = net_open_socket(host, port); + #ifdef _WIN32 + net_init_WSA(); + console_handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (console_handle == INVALID_HANDLE_VALUE) console_handle = NULL; + #endif - /* auth & commands */ - if(rcon_auth(rsock, pass)) - { - if(terminal_mode) - ret = run_terminal_mode(rsock); - else - ret = run_commands(argc, argv); - } - else /* auth failed */ - { - ret = -1; - fprintf(stdout, "Authentication failed!\n"); - } + // open socket + rsock = net_connect(host, port); - /* cleanup */ - net_close_socket(rsock); - rsock = -1; + // auth & commands + if (rcon_auth(rsock, pass)) + { + if (terminal_mode) + ret = run_terminal_mode(rsock); + else + ret = run_commands(argc, argv); + } + else // auth failed + { + ret = -1; + fprintf(stdout, "Authentication failed!\n"); + } - return ret; + net_close(rsock); + rsock = -1; + + return ret; } void usage(void) { - fputs( - "Usage: "IN_NAME" [OPTIONS]... [COMMANDS]...\n" - "Sends rcon commands to minecraft server.\n\n" - "Option:\n" - " -h\t\tPrints usage.\n" - " -s\t\tSilent mode. Do not print data received from rcon.\n" - " -t\t\tTerminal mode. Acts as interactive terminal.\n" - " -p\t\tRcon password. Default: \"\".\n" - " -H\t\tHost address or ip.\n" - " -P\t\tPort. Default: 25575.\n" - " -c\t\tDo not print colors. Disables bukkit color printing.\n" - " -r\t\tPrint everything in raw mode.\n\t\tGood for debugging and custom handling of the output.\n" - ,stdout); + fputs( + "Usage: "IN_NAME" [OPTIONS]... [COMMANDS]...\n\n" + "Sends rcon commands to Minecraft server.\n\n" + "Option:\n" + " -h\t\tPrint usage\n" + " -H\t\tServer address\n" + " -P\t\tPort (default is 25575)\n" + " -p\t\tRcon password\n" + " -t\t\tInteractive terminal mode\n" + " -s\t\tSilent mode (do not print received packets)\n" + " -c\t\tDisable colors\n" + " -r\t\tOutput raw packets (debugging and custom handling)\n" + " -v\t\tOutput version information\n" + ,stdout + ); - puts("\nInvidual commands must be separated with spaces.\n"); - puts("Example:\n "IN_NAME" -c -H 192.168.1.42 -P 9999 -p password cmd1 \"cmd2 with spaces\"\n"); - puts("minecraft rcon ("IN_NAME") "VERSION".\nReport bugs to tiiffi_at_gmail_dot_com.\n"); + puts("\nCommands with arguments 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_at_gmail_dot_com or https://github.com/Tiiffi/mcrcon/issues/\n"); - #ifdef _WIN32 - puts("Press enter to exit."); - getchar(); - #endif - exit(0); -} + #ifdef _WIN32 + puts("Press enter to exit."); + getchar(); + #endif -void error(char *errstring) -{ - fputs(errstring, stderr); - exit(-1); + exit(0); } #ifdef _WIN32 void net_init_WSA(void) { - WSADATA wsadata; - int err; + WSADATA wsadata; - err = WSAStartup(MAKEWORD(1, 1), &wsadata); - if(err != 0) - { - fprintf(stderr, "WSAStartup failed. Errno: %d.\n", err); - exit(-1); - } + // Request winsock 2.2 for now. + // Should be compatible down to Win XP. + WORD version = MAKEWORD(2, 2); + + int err = WSAStartup(version, &wsadata); + if (err != 0) + { + fprintf(stderr, "WSAStartup failed. Error: %d.\n", err); + exit(-1); + } } #endif -struct addrinfo *net_resolve(char *host, char *port) -{ - /* !!! This function should be integrated to open_socket function for cleaner code !!! */ - - //struct in_addr6 serveraddr; - struct addrinfo hints, *result; - int ret; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - /* hints.ai_flags = AI_NUMERICSERV; // Windows retardism */ - - ret = getaddrinfo(host, port, &hints, &result); - if(ret != 0) - { - if(ret == EAI_SERVICE) fprintf(stderr, "Invalid port (%s).\n", port); - fprintf(stderr, "Error: Unable to resolve hostname (%s).\n", host); - exit(-1); - } - - return result; -} - /* socket close and cleanup */ -void net_close_socket(int sd) +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 */ -int net_open_socket(char *host, char *port) +/* http://man7.org/linux/man-pages/man3/getaddrinfo.3.html */ +/* https://bugs.chromium.org/p/chromium/issues/detail?id=44489 */ +int net_connect(const char *host, const char *port) { - int sd; + int sd; - struct addrinfo *serverinfo; + struct addrinfo hints = {0}; + struct addrinfo *server_info, *p; - serverinfo = net_resolve(host, port); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; - sd = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol); - if(sd < 0) + #ifdef _WIN32 + net_init_WSA(); + #endif + + // Get host address info + int ret = getaddrinfo(host, port, &hints, &server_info); + if (ret != 0) { - #ifdef _WIN32 - WSACleanup(); - #endif - freeaddrinfo(serverinfo); - error("Error: cannot create socket.\n"); + 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); } - if(connect(sd, serverinfo->ai_addr, serverinfo->ai_addrlen) != 0) - { - net_close_socket(sd); - fprintf(stderr, "Error: connection failed (%s).\n", host); - freeaddrinfo(serverinfo); - exit(-1); - } + // Go through the hosts and try to connect + 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; - freeaddrinfo(serverinfo); - return sd; + ret = connect(sd, p->ai_addr, p->ai_addrlen); + if (ret == -1) + { + net_close(sd); + continue; + } + // Get out of the loop when connect is successful + break; + } + + 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; +} + +int net_send(int sd, const uint8_t *buff, size_t size) +{ + size_t sent = 0; + size_t left = size; + + while (sent < size) + { + int result = send(sd, (const char *) buff + sent, left, 0); + + if (result == -1) return -1; + + sent += result; + left -= sent; + } + + return 0; } int net_send_packet(int sd, rc_packet *packet) { - int len; - int total = 0; /* how many bytes we've sent */ - int bytesleft; /* how many we have left to send */ - int ret = -1; + int len; + int total = 0; /* how many bytes we've sent */ + int bytesleft; /* how many we have left to send */ + int ret = -1; - bytesleft = len = packet->size + sizeof(int); + 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; - } + while(total < len) + { + ret = send(sd, (char *) packet + total, bytesleft, 0); + if(ret == -1) { break; } + total += ret; + bytesleft -= ret; + } - /* return -1 on failure, 0 on success */ - return ret == -1 ? -1 : 1; + /* return -1 on failure, 0 on success */ + return ret == -1 ? -1 : 1; } rc_packet *net_recv_packet(int sd) { - int psize; - static rc_packet packet = {0, 0, 0, { 0x00 }}; + int psize; + static rc_packet packet = {0, 0, 0, { 0x00 }}; - /* packet.size = packet.id = packet.cmd = 0; */ + /* packet.size = packet.id = packet.cmd = 0; */ - int ret = recv(sd, (char *) &psize, sizeof(int), 0); + int ret = recv(sd, (char *) &psize, sizeof(int), 0); - if(ret == 0) { - fprintf(stderr, "Connection lost.\n"); - connection_alive = 0; - return NULL; - } + if (ret == 0) + { + fprintf(stderr, "Connection lost.\n"); + connection_alive = 0; + return NULL; + } - if(ret != sizeof(int)) { - fprintf(stderr, "Error: recv() failed. Invalid packet size (%d).\n", ret); - connection_alive = 0; - return NULL; - } + if (ret != sizeof(int)) + { + fprintf(stderr, "Error: recv() failed. Invalid packet size (%d).\n", ret); + 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); - if(psize > DATA_BUFFSIZE || psize < 0) psize = DATA_BUFFSIZE; - net_clean_incoming(sd, psize); - 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); - packet.size = psize; + if(psize > DATA_BUFFSIZE || psize < 0) psize = DATA_BUFFSIZE; + net_clean_incoming(sd, psize); - ret = recv(sd, (char *) &packet + sizeof(int), psize, 0); - if(ret == 0) { - fprintf(stderr, "Connection lost.\n"); - connection_alive = 0; - return NULL; - } - if(ret != psize) { - 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 NULL; + } - return &packet; + packet.size = psize; + + ret = recv(sd, (char *) &packet + sizeof(int), psize, 0); + if (ret == 0) + { + fprintf(stderr, "Connection lost.\n"); + connection_alive = 0; + return NULL; + } + + if(ret != psize) + { + 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; } 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) { - fprintf(stderr, "Connection lost.\n"); - connection_alive = 0; - } + if(ret == 0) + { + fprintf(stderr, "Connection lost.\n"); + connection_alive = 0; + } - return ret; + return ret; } void print_color(int color) { - /* sh compatible color array */ - #ifndef _WIN32 - char *colors[] = { - "\033[0;30m", /* 00 BLACK 0x30 */ - "\033[0;34m", /* 01 BLUE 0x31 */ - "\033[0;32m", /* 02 GREEN 0x32 */ - "\033[0;36m", /* 03 CYAN 0x33 */ - "\033[0;31m", /* 04 RED 0x34 */ - "\033[0;35m", /* 05 PURPLE 0x35 */ - "\033[0;33m", /* 06 GOLD 0x36 */ - "\033[0;37m", /* 07 GREY 0x37 */ - "\033[1;30m", /* 08 DGREY 0x38 */ - "\033[1;34m", /* 09 LBLUE 0x39 */ - "\033[1;32m", /* 10 LGREEN 0x61 */ - "\033[1;36m", /* 11 LCYAN 0x62 */ - "\033[1;31m", /* 12 LRED 0x63 */ - "\033[1;35m", /* 13 LPURPLE 0x64 */ - "\033[1;33m", /* 14 YELLOW 0x65 */ - "\033[1;37m", /* 15 WHITE 0x66 */ - }; + /* sh compatible color array */ + #ifndef _WIN32 + char *colors[] = + { + "\033[0;30m", /* 00 BLACK 0x30 */ + "\033[0;34m", /* 01 BLUE 0x31 */ + "\033[0;32m", /* 02 GREEN 0x32 */ + "\033[0;36m", /* 03 CYAN 0x33 */ + "\033[0;31m", /* 04 RED 0x34 */ + "\033[0;35m", /* 05 PURPLE 0x35 */ + "\033[0;33m", /* 06 GOLD 0x36 */ + "\033[0;37m", /* 07 GREY 0x37 */ + "\033[1;30m", /* 08 DGREY 0x38 */ + "\033[1;34m", /* 09 LBLUE 0x39 */ + "\033[1;32m", /* 10 LGREEN 0x61 */ + "\033[1;36m", /* 11 LCYAN 0x62 */ + "\033[1;31m", /* 12 LRED 0x63 */ + "\033[1;35m", /* 13 LPURPLE 0x64 */ + "\033[1;33m", /* 14 YELLOW 0x65 */ + "\033[1;37m", /* 15 WHITE 0x66 */ + }; - if(color == 0) { - fputs("\033[0m", stdout); /* CANCEL COLOR */ - } - else - #endif - { - if(color >= 0x61 && color <= 0x66) color -= 0x57; - else if(color >= 0x30 && color <= 0x39) color -= 0x30; - else return; + if(color == 0) + { + fputs("\033[0m", stdout); /* CANCEL COLOR */ + } + else + #endif + { + if(color >= 0x61 && color <= 0x66) color -= 0x57; + else if(color >= 0x30 && color <= 0x39) color -= 0x30; + else return; - #ifndef _WIN32 - fputs(colors[color], stdout); - #else - SetConsoleTextAttribute(console_handle, color); - #endif - } + #ifndef _WIN32 + fputs(colors[color], stdout); + #else + SetConsoleTextAttribute(console_handle, color); + #endif + } } /* this hacky mess might use some optimizing */ void packet_print(rc_packet *packet) { - if (raw_output == 1) { - for(int i = 0; packet->data[i] != 0; ++i) putchar(packet->data[i]); - return; - } - - int i; - int def_color = 0; + if (raw_output == 1) + { + for(int i = 0; packet->data[i] != 0; ++i) putchar(packet->data[i]); + return; + } - #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; - #endif + int i; + int def_color = 0; - /* colors enabled so try to handle the bukkit colors for terminal */ - if(print_colors == 1) { + #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; + #endif - for(i = 0; (unsigned char) packet->data[i] != 0; ++i) { - if((unsigned char) packet->data[i] == 0xa7) { - ++i; - print_color(packet->data[i]); - continue; - } - if(packet->data[i] == 0x0A) print_color(def_color); + /* colors enabled so try to handle the bukkit colors for terminal */ + if (print_colors == 1) { - putchar(packet->data[i]); - } - print_color(def_color); /* cancel coloring */ + for (i = 0; (unsigned char) packet->data[i] != 0; ++i) + { + if ((unsigned char) packet->data[i] == 0xa7) + { + ++i; + print_color(packet->data[i]); + continue; + } + if (packet->data[i] == 0x0A) print_color(def_color); + + putchar(packet->data[i]); + } + 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] == 0xa7) { - ++i; - continue; - } - putchar(packet->data[i]); - } - } + } + /* strip colors */ + else + { + for (i = 0; (unsigned char) packet->data[i] != 0; ++i) + { + if ((unsigned char) packet->data[i] == 0xa7) + { + ++i; + continue; + } + + putchar(packet->data[i]); + } + } - /* print newline if string has no newline */ - if(packet->data[i-1] != 10 && packet->data[i-1] != 13) - putchar('\n'); + /* print newline if string has no newline */ + if (packet->data[i-1] != 10 && packet->data[i-1] != 13) + putchar('\n'); } rc_packet *packet_build(int id, int cmd, char *s1) -{ /* hacky function */ - static rc_packet packet = {0, 0, 0, { 0x00 }}; +{ /* hacky function */ + static rc_packet packet = {0, 0, 0, { 0x00 }}; - /* 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); - return NULL; - } + /* 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); + return NULL; + } - packet.size = sizeof(int) * 2 + s1_len + 2; - packet.id = id; - packet.cmd = cmd; - strncpy(packet.data, s1, DATA_BUFFSIZE); + packet.size = sizeof(int) * 2 + s1_len + 2; + packet.id = id; + packet.cmd = cmd; + strncpy(packet.data, s1, DATA_BUFFSIZE); - return &packet; + return &packet; +} + +// TODO(Tiiffi): String length limit? +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; - rc_packet *packet = packet_build(RCON_PID, RCON_AUTHENTICATE, passwd); - if(packet == NULL) return 0; + rc_packet *packet = packet_build(RCON_PID, RCON_AUTHENTICATE, passwd); + if (packet == NULL) + return 0; - ret = net_send_packet(rsock, packet); - if(!ret) return 0; /* send failed */ + ret = net_send_packet(rsock, packet); + if (!ret) + return 0; /* send failed */ - packet = net_recv_packet(rsock); - if(packet == NULL) return 0; + packet = net_recv_packet(rsock); + if (packet == NULL) + return 0; - /* return 1 if authentication OK */ - return packet->id == -1 ? 0 : 1; + /* return 1 if authentication OK */ + return packet->id == -1 ? 0 : 1; } int rcon_command(int rsock, char *command) { - int ret; + int ret; (void) ret; - rc_packet *packet = packet_build(RCON_PID, RCON_EXEC_COMMAND, command); - if(packet == NULL) { - connection_alive = 0; - return 0; - } + size_t size; + uint8_t *p = packet_build_malloc(&size, RCON_PID, RCON_EXEC_COMMAND, command); + if (p == NULL) + { + connection_alive = 0; + return 0; + } - ret = net_send_packet(rsock, packet); - if(!ret) return 0; /* send failed */ + net_send(rsock, p, size); - packet = net_recv_packet(rsock); - if(packet == NULL) return 0; + free(p); - if(packet->id != RCON_PID) return 0; /* wrong packet id */ + //ret = net_send_packet(rsock, packet); + //if(!ret) return 0; /* send failed */ - if(!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); - } + rc_packet *packet; + packet = net_recv_packet(rsock); + if (packet == NULL) + return 0; - /* return 1 if world was saved */ - return 1; + if (packet->id != RCON_PID) + return 0; /* wrong packet id */ + + if (!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); + } + + /* return 1 if world was saved */ + return 1; } int run_commands(int argc, char *argv[]) { - int i, ok = 1, ret = 0; + int i, ok = 1, ret = 0; - for(i = optind; i < argc && ok; i++) { - ok = rcon_command(rsock, argv[i]); - ret += ok; - } + for (i = optind; i < argc && ok; i++) + { + ok = rcon_command(rsock, argv[i]); + ret += ok; + } - return ret; + return ret; } /* interactive terminal mode */ int run_terminal_mode(int rsock) { - int ret = 0; - char command[DATA_BUFFSIZE] = {0x00}; + int ret = 0; + char command[DATA_BUFFSIZE] = {0x00}; - puts("Logged in. Type \"Q\" to quit!"); + puts("Logged in. Type \"Q\" to quit!"); - while(connection_alive) { + while (connection_alive) + { + int len = get_line(command, DATA_BUFFSIZE); + if(command[0] == 'Q' && command[1] == 0) + break; - int len = get_line(command, DATA_BUFFSIZE); - if(command[0] == 'Q' && command[1] == 0) break; + if(len > 0 && connection_alive) + ret += rcon_command(rsock, command); - if(len > 0 && connection_alive) ret += rcon_command(rsock, command); + command[0] = len = 0; + } - command[0] = len = 0; - } - - return ret; + return ret; } -/* gets line from stdin and deals with rubbish left in input buffer */ +// gets line from stdin and deals with rubbish left in the input buffer int get_line(char *buffer, int bsize) { - int ch, len; + int ch, len; - fputs("> ", stdout); - fgets(buffer, bsize, stdin); + fputs("/", stdout); + (void) fgets(buffer, bsize, stdin); - if(buffer[0] == 0) connection_alive = 0; + if (buffer[0] == 0) + connection_alive = 0; - /* remove unwanted characters from the buffer */ - buffer[strcspn(buffer, "\r\n")] = '\0'; + // remove unwanted characters from the buffer + buffer[strcspn(buffer, "\r\n")] = '\0'; - len = strlen(buffer); + len = strlen(buffer); - /* clean input buffer if needed */ - if(len == bsize - 1) - while ((ch = getchar()) != '\n' && ch != EOF); + // clean input buffer if needed + if (len == bsize - 1) + 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; }