mirror of
				https://github.com/Tiiffi/mcrcon.git
				synced 2025-10-30 21:01:07 -04:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			dba07aacf7
			...
			b1b46ca08c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b1b46ca08c | |||
| 2bb1fafdaa | |||
| 0fb17971c0 | |||
| ec11d77e89 | |||
| 2d29741691 | |||
| cc77044df1 | 
| @ -2,10 +2,11 @@ | ||||
|  | ||||
| ###### 0.8.0 | ||||
|  - Implement support for multipacket responses | ||||
|  - Add support to Valve style rcon authentication | ||||
|  - Add support for Valve style rcon authentication | ||||
|  - Add experimental UTF-8 support for Windows | ||||
|  - Change maximum packet size to correct value (4096 -> 4106) | ||||
|  - Attempt to add missing newlines in Minecraft servers | ||||
|     * Currently implemented only for the 'help' command | ||||
|  - Attempt to add missing newlines in bugged Minecraft servers | ||||
|     * Implemented for responses to the 'help' command and unknown commands | ||||
|  - Print auth failed message to stderr instead of stdout | ||||
|  - Fail immediately if received packet size is out of spec | ||||
|  - Return proper exit code from run_terminal_mode() | ||||
|  | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -15,7 +15,7 @@ RM = rm -v -f | ||||
|  | ||||
| CC ?= gcc | ||||
| CFLAGS = -std=gnu99 -Wall -Wextra -Wpedantic -Wno-gnu-zero-variadic-macro-arguments -O2 | ||||
| EXTRAFLAGS ?= -fstack-protector-all | ||||
| EXTRAFLAGS ?= -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -pie -Wl,-z,relro -Wl,-z,now -fno-common | ||||
|  | ||||
| ifeq ($(OS), Windows_NT) | ||||
| 	LINKER = -lws2_32 | ||||
|  | ||||
							
								
								
									
										201
									
								
								mcrcon.c
									
									
									
									
									
								
							
							
						
						
									
										201
									
								
								mcrcon.c
									
									
									
									
									
								
							| @ -31,14 +31,17 @@ | ||||
| #include <errno.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| 	#include <winsock2.h> | ||||
|     #include <windows.h> | ||||
|     #include <ws2tcpip.h> | ||||
| 	#include <windows.h> | ||||
| 	#include <ws2tcpip.h> | ||||
| 	#include <fcntl.h> | ||||
| 	#include <wchar.h> | ||||
| #else | ||||
|     #include <sys/socket.h> | ||||
|     #include <netdb.h> | ||||
| 	#include <sys/socket.h> | ||||
| 	#include <sys/select.h> | ||||
| 	#include <netdb.h> | ||||
| 	#include <termios.h> | ||||
| #endif | ||||
|  | ||||
| #define VERSION "0.8.0" | ||||
| @ -56,15 +59,17 @@ | ||||
| #define MAX_PACKET_SIZE		4106 // id (4) + cmd (4) + DATA_BUFFSIZE | ||||
| #define MIN_PACKET_SIZE		10   // id (4) + cmd (4) + two empty strings (2) | ||||
|  | ||||
| #define MAX_WAIT_TIME 600 | ||||
|  | ||||
| #define log_error(fmt, ...) fprintf(stderr, fmt,  ##__VA_ARGS__); | ||||
|  | ||||
| // rcon packet structure | ||||
| typedef struct { | ||||
|     int32_t size; | ||||
|     int32_t id; | ||||
|     int32_t cmd; | ||||
|     uint8_t data[DATA_BUFFSIZE]; | ||||
|     // ignoring string2 for now | ||||
| 	int32_t size; | ||||
| 	int32_t id; | ||||
| 	int32_t cmd; | ||||
| 	uint8_t data[DATA_BUFFSIZE]; | ||||
| 	// ignoring string2 for now | ||||
| } rc_packet; | ||||
|  | ||||
| // =================================== | ||||
| @ -108,8 +113,12 @@ static bool global_minecraft_newline_fix = false; | ||||
| static int  global_rsock; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|   // console coloring on windows | ||||
|   HANDLE console_handle; | ||||
| // console coloring on windows | ||||
| HANDLE console_handle; | ||||
|  | ||||
| // console code pages | ||||
| UINT old_output_codepage; | ||||
| UINT old_input_codepage; | ||||
| #endif | ||||
|  | ||||
| // safety stuff (windows is still misbehaving) | ||||
| @ -117,6 +126,15 @@ void exit_proc(void) | ||||
| { | ||||
| 	if (global_rsock != -1) | ||||
| 		net_close(global_rsock); | ||||
|  | ||||
| 	#ifdef _WIN32 | ||||
| 	// Restore previous code pages | ||||
| 	SetConsoleOutputCP(old_output_codepage); | ||||
| 	SetConsoleCP(old_input_codepage); | ||||
|  | ||||
| 	// Set back to binary mode | ||||
| 	_setmode(_fileno(stdin), _O_BINARY); | ||||
| 	#endif | ||||
| } | ||||
|  | ||||
| // TODO: check exact windows and linux behaviour | ||||
| @ -126,13 +144,9 @@ void sighandler(int sig) | ||||
| 		putchar('\n'); | ||||
|  | ||||
| 	global_connection_alive = 0; | ||||
| 	#ifndef _WIN32 | ||||
| 	    exit(EXIT_SUCCESS); | ||||
| 	#endif | ||||
| 	exit(EXIT_SUCCESS); | ||||
| } | ||||
|  | ||||
| #define MAX_WAIT_TIME 600 | ||||
|  | ||||
| unsigned int mcrcon_parse_seconds(char *str) | ||||
| { | ||||
| 	char *end; | ||||
| @ -185,7 +199,7 @@ int main(int argc, char *argv[]) | ||||
| 			case 'r': flag_raw_output = 1;           break; | ||||
| 			case 'w': | ||||
| 				flag_wait_seconds = mcrcon_parse_seconds(optarg); | ||||
| 			break; | ||||
| 				break; | ||||
|  | ||||
| 			case 'v': | ||||
| 				puts(VER_STR); | ||||
| @ -216,10 +230,20 @@ 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; | ||||
|  | ||||
| 	// Set the output and input code pages to utf-8 | ||||
| 	old_output_codepage = GetConsoleOutputCP(); | ||||
| 	old_input_codepage = GetConsoleCP(); | ||||
|  | ||||
| 	SetConsoleOutputCP(CP_UTF8); | ||||
| 	SetConsoleCP(CP_UTF8); | ||||
|  | ||||
| 	// Set the file translation mode to UTF16 | ||||
| 	_setmode(_fileno(stdin), _O_U16TEXT); | ||||
| 	#endif | ||||
|  | ||||
| 	// open socket | ||||
| @ -270,8 +294,8 @@ void usage(void) | ||||
| 	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."); | ||||
| 	    getchar(); | ||||
| 	puts("Press enter to exit."); | ||||
| 	getchar(); | ||||
| 	#endif | ||||
|  | ||||
| 	exit(EXIT_SUCCESS); | ||||
| @ -298,10 +322,10 @@ void net_init_WSA(void) | ||||
| void net_close(int sd) | ||||
| { | ||||
| 	#ifdef _WIN32 | ||||
| 		closesocket(sd); | ||||
| 		WSACleanup(); | ||||
| 	closesocket(sd); | ||||
| 	WSACleanup(); | ||||
| 	#else | ||||
| 		close(sd); | ||||
| 	close(sd); | ||||
| 	#endif | ||||
| } | ||||
|  | ||||
| @ -320,16 +344,16 @@ int net_connect(const char *host, const char *port) | ||||
| 	hints.ai_protocol = IPPROTO_TCP; | ||||
|  | ||||
| 	#ifdef _WIN32 | ||||
| 	  net_init_WSA(); | ||||
| 	net_init_WSA(); | ||||
| 	#endif | ||||
|  | ||||
| 	int ret = getaddrinfo(host, port, &hints, &server_info); | ||||
| 	if (ret != 0) { | ||||
| 		log_error("Name resolution failed.\n"); | ||||
| 		#ifdef _WIN32 | ||||
| 			log_error("Error %d: %s", ret, gai_strerror(ret)); | ||||
| 		log_error("Error %d: %s", ret, gai_strerror(ret)); | ||||
| 		#else | ||||
| 			log_error("Error %d: %s\n", ret, gai_strerror(ret)); | ||||
| 		log_error("Error %d: %s\n", ret, gai_strerror(ret)); | ||||
| 		#endif | ||||
|  | ||||
| 		exit(EXIT_FAILURE); | ||||
| @ -355,7 +379,7 @@ int net_connect(const char *host, const char *port) | ||||
| 		/* TODO (Tiiffi): Check why windows does not report errors */ | ||||
| 		log_error("Connection failed.\n"); | ||||
| 		#ifndef _WIN32 | ||||
| 			log_error("Error %d: %s\n", errno, strerror(errno)); | ||||
| 		log_error("Error %d: %s\n", errno, strerror(errno)); | ||||
| 		#endif | ||||
|  | ||||
| 		freeaddrinfo(server_info); | ||||
| @ -386,6 +410,9 @@ bool net_send_packet(int sd, rc_packet *packet) | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| // TODO: Fix the wonky behaviour when server quit/exit/stop/close | ||||
| //       command is issued. Client should close gracefully without errors. | ||||
| rc_packet *net_recv_packet(int sd) | ||||
| { | ||||
| 	int32_t psize; | ||||
| @ -467,9 +494,9 @@ void print_color(int color) | ||||
| 		else return; | ||||
|  | ||||
| 		#ifndef _WIN32 | ||||
| 			fputs(colors[color], stdout); | ||||
| 		fputs(colors[color], stdout); | ||||
| 		#else | ||||
| 			SetConsoleTextAttribute(console_handle, color); | ||||
| 		SetConsoleTextAttribute(console_handle, color); | ||||
| 		#endif | ||||
| 	} | ||||
| } | ||||
| @ -478,20 +505,32 @@ void print_color(int color) | ||||
| void packet_print(rc_packet *packet) | ||||
| { | ||||
| 	uint8_t *data = packet->data; | ||||
| 	int i; | ||||
|  | ||||
| 	if (flag_raw_output == 1) { | ||||
| 		fputs((char *) data, stdout); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	int i; | ||||
| 	// Newline fix for Minecraft | ||||
| 	if (global_valve_protocol == false) { | ||||
| 		const char test[] = "Unknown or incomplete command, see below for error"; | ||||
| 		size_t test_size = sizeof test - 1; | ||||
| 		if (strncmp((char *) data, test, test_size) == 0) { | ||||
| 			fwrite(data, test_size, 1, stdout); | ||||
| 			putchar('\n'); | ||||
| 			data = &data[test_size]; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	int default_color = 0; | ||||
|  | ||||
| 	#ifdef _WIN32 | ||||
| 		CONSOLE_SCREEN_BUFFER_INFO console_info; | ||||
| 		if (GetConsoleScreenBufferInfo(console_handle, &console_info) != 0) { | ||||
| 			default_color = console_info.wAttributes + 0x30; | ||||
| 		} else default_color = 0x37; | ||||
| 	CONSOLE_SCREEN_BUFFER_INFO console_info; | ||||
| 	if (GetConsoleScreenBufferInfo(console_handle, &console_info) != 0) { | ||||
| 		default_color = console_info.wAttributes + 0x30; | ||||
| 	} | ||||
| 	else default_color = 0x37; | ||||
| 	#endif | ||||
|  | ||||
| 	bool slash = false; | ||||
| @ -512,6 +551,7 @@ void packet_print(rc_packet *packet) | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		// Add missing newlines | ||||
| 		if (colors_detected == false && global_minecraft_newline_fix && data[i] == '/') { | ||||
| 			slash ? putchar('\n') : (slash = true); | ||||
| 		} | ||||
| @ -563,7 +603,7 @@ bool rcon_auth(int sock, char *passwd) | ||||
| 		return 0; // send failed | ||||
| 	} | ||||
|  | ||||
| receive: | ||||
| 	receive: | ||||
| 	packet = net_recv_packet(sock); | ||||
| 	if (packet == NULL) | ||||
| 		return 0; | ||||
| @ -638,7 +678,7 @@ int rcon_command(int sock, char *command) | ||||
|  | ||||
| 		if (flag_silent_mode == false) { | ||||
| 			if (packet->size > 10) | ||||
| 			packet_print(packet); | ||||
| 				packet_print(packet); | ||||
| 		} | ||||
|  | ||||
| 		int result = select(sock + 1, &read_fds, NULL, NULL, &timeout); | ||||
| @ -669,9 +709,9 @@ int run_commands(int argc, char *argv[]) | ||||
|  | ||||
| 		if (flag_wait_seconds > 0) { | ||||
| 			#ifdef _WIN32 | ||||
| 				Sleep(flag_wait_seconds * 1000); | ||||
| 			Sleep(flag_wait_seconds * 1000); | ||||
| 			#else | ||||
| 				sleep(flag_wait_seconds); | ||||
| 			sleep(flag_wait_seconds); | ||||
| 			#endif | ||||
| 		} | ||||
| 	} | ||||
| @ -682,7 +722,7 @@ int run_terminal_mode(int sock) | ||||
| { | ||||
| 	char command[MAX_COMMAND_LENGTH] = {0}; | ||||
|  | ||||
| 	puts("Logged in.\nType 'Q' or press Ctrl-D / Ctrl-C to disconnect."); | ||||
| 	puts("Logged in. Press Ctrl-D or Ctrl-C to disconnect."); | ||||
|  | ||||
| 	while (global_connection_alive) { | ||||
| 		putchar('>'); | ||||
| @ -691,8 +731,6 @@ int run_terminal_mode(int sock) | ||||
| 		int len = get_line(command, MAX_COMMAND_LENGTH); | ||||
| 		if (len < 1) continue; | ||||
|  | ||||
| 		if (strcasecmp(command, "Q") == 0) break; | ||||
|  | ||||
| 		if (len > 0 && global_connection_alive) { | ||||
| 			if (!rcon_command(sock, command)) { | ||||
| 				return EXIT_FAILURE; | ||||
| @ -702,22 +740,80 @@ int run_terminal_mode(int sock) | ||||
| 		/* 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. | ||||
| 		 * NOTE: Not sure if this still a problem?! | ||||
| 		 */ | ||||
| 		if (strcasecmp(command, "stop") == 0) { | ||||
| 			break; | ||||
| 		if (global_valve_protocol == false && strcasecmp(command, "stop") == 0) { | ||||
| 			// Timeout to before checking if connection was closed | ||||
| 			#ifdef _WIN32 | ||||
| 			Sleep(2 * 1000); | ||||
| 			#else | ||||
| 			sleep(2); | ||||
| 			#endif | ||||
|  | ||||
| 			char tmp[1]; | ||||
| 			#ifdef _WIN32 | ||||
| 			// TODO: More Windows side testing! | ||||
| 			int result = recv(sock, tmp, sizeof(tmp), MSG_PEEK | 0); | ||||
| 			#else | ||||
| 			int result = recv(sock, tmp, sizeof(tmp), MSG_PEEK | MSG_DONTWAIT); | ||||
| 			#endif | ||||
| 			if (result == 0) { | ||||
| 				break; // Connection closed | ||||
| 			} | ||||
| 			// TODO: Check for return values and errors! | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return EXIT_SUCCESS; | ||||
| } | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| char *utf8_getline(char *buf, int size, FILE *stream) | ||||
| { | ||||
| 	// Widechar fgets | ||||
| 	wchar_t in[MAX_COMMAND_LENGTH] = {0}; | ||||
|  | ||||
| 	wchar_t *result = fgetws(in, MAX_COMMAND_LENGTH, stream); | ||||
| 	if (result == NULL) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	// Calculates UTF-8 buffer size | ||||
| 	int required_size = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); | ||||
| 	if (size < required_size) { | ||||
| 		// TODO: Proper error handling & reporting | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	if (WideCharToMultiByte(CP_UTF8, 0, in, -1, buf, size, NULL, NULL) == 0) { | ||||
| 		// TODO: Proper error handling & reporting | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	return buf; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void flush_input(void) | ||||
| { | ||||
| 	#ifdef _WIN32 | ||||
| 	FlushConsoleInputBuffer(console_handle); | ||||
| 	#else | ||||
| 	tcflush(STDIN_FILENO, TCIFLUSH); | ||||
| 	#endif | ||||
| } | ||||
|  | ||||
| // gets line from stdin and deals with rubbish left in the input buffer | ||||
| int get_line(char *buffer, int bsize) | ||||
| { | ||||
| 	#ifdef _WIN32 | ||||
| 	char *ret = utf8_getline(buffer, bsize, stdin); | ||||
| 	#else | ||||
| 	char *ret = fgets(buffer, bsize, stdin); | ||||
| 	#endif | ||||
|  | ||||
| 	flush_input(); | ||||
|  | ||||
| 	if (ret == NULL) { | ||||
| 		if (ferror(stdin)) { | ||||
| 			log_error("Error %d: %s\n", errno, strerror(errno)); | ||||
| @ -729,14 +825,17 @@ int get_line(char *buffer, int bsize) | ||||
|  | ||||
| 	// remove unwanted characters from the buffer | ||||
| 	buffer[strcspn(buffer, "\r\n")] = '\0'; | ||||
|  | ||||
| 	int len = strlen(buffer); | ||||
|  | ||||
| #if 0 | ||||
| 	// clean input buffer if needed | ||||
| 	#ifndef _WIN32 | ||||
| 	if (len == bsize - 1) { | ||||
| 		int ch; | ||||
| 		while ((ch = getchar()) != '\n' && ch != EOF); | ||||
| 	} | ||||
| 	#endif | ||||
| #endif | ||||
|  | ||||
| 	return len; | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	