Implemented proof of concept RCON command

This commit is contained in:
MicLieg 2024-02-06 00:58:59 +01:00 committed by MicLieg
parent ad2f81599a
commit 212e02e16e
5 changed files with 180 additions and 4 deletions

View File

@ -47,7 +47,7 @@ if [ "$(whoami)" != "root" ]; then
done
fi
allowed_commands_array=(BACKUP CONSOLE DEBUG DETAILS MAP-COMPRESSOR FASTDL MODS-INSTALL MODS-REMOVE MODS-UPDATE MONITOR POST-DETAILS RESTART START STOP TEST-ALERT CHANGE-PASSWORD UPDATE UPDATE-LGSM VALIDATE WIPE)
allowed_commands_array=(BACKUP CONSOLE DEBUG DETAILS MAP-COMPRESSOR FASTDL MODS-INSTALL MODS-REMOVE MODS-UPDATE MONITOR POST-DETAILS RCON RESTART START STOP TEST-ALERT CHANGE-PASSWORD UPDATE UPDATE-LGSM VALIDATE WIPE)
for allowed_command in "${allowed_commands_array[@]}"; do
if [ "${allowed_command}" == "${commandname}" ]; then
check_logs.sh
@ -61,14 +61,14 @@ for allowed_command in "${allowed_commands_array[@]}"; do
fi
done
allowed_commands_array=(CONSOLE DEBUG MONITOR START STOP)
allowed_commands_array=(CONSOLE DEBUG MONITOR RCON START STOP)
for allowed_command in "${allowed_commands_array[@]}"; do
if [ "${allowed_command}" == "${commandname}" ]; then
check_config.sh
fi
done
allowed_commands_array=(DEBUG DETAILS DEV-QUERY-RAW MONITOR POST_DETAILS START STOP POST-DETAILS)
allowed_commands_array=(DEBUG DETAILS DEV-QUERY-RAW MONITOR POST_DETAILS RCON START STOP POST-DETAILS)
for allowed_command in "${allowed_commands_array[@]}"; do
if [ "${allowed_command}" == "${commandname}" ]; then
if [ -z "${installflag}" ]; then
@ -86,7 +86,7 @@ for allowed_command in "${allowed_commands_array[@]}"; do
fi
done
allowed_commands_array=(CHANGE-PASSWORD DETAILS MONITOR START STOP UPDATE VALIDATE POST-DETAILS)
allowed_commands_array=(CHANGE-PASSWORD DETAILS MONITOR RCON START STOP UPDATE VALIDATE POST-DETAILS)
for allowed_command in "${allowed_commands_array[@]}"; do
if [ "${allowed_command}" == "${commandname}" ]; then
check_status.sh

View File

@ -0,0 +1,44 @@
#!/bin/bash
# LinuxGSM command_rcon.sh module
# Author: Daniel Gibbs
# Contributors: http://linuxgsm.com/contrib
# Website: https://linuxgsm.com
# Description: Send rcon commands to different gameservers.
commandname="RCON"
commandaction="Rcon"
moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
fn_firstcommand_set
check.sh
if [ "${status}" == "0" ]; then
fn_print_error_nl "Server not running"
fn_script_log_error "Failed to access: Server not running"
if fn_prompt_yn "Do you want to start the server?" Y; then
exitbypass=1
command_start.sh
fi
fi
if [ -n "${userinput2}" ]; then
rconcommandtosend="${userinput2}"
else
fn_print_header
fn_print_information_nl "Send a RCON command to the server."
echo ""
rconcommandtosend=$(fn_prompt_message "RCON command: ")
fi
fn_print_dots "Sending RCON command to server: \"${rconcommandtosend}\""
if [ ! -f "${modulesdir}/rcon.py" ]; then
fn_fetch_file_github "lgsm/modules" "rcon.py" "${modulesdir}" "chmodx" "norun" "noforce" "nohash"
fi
"${modulesdir}"/rcon.py -a "${telnetip}" -p "${rconport}" -P "${rconpassword}" -c "${rconcommandtosend}" > /dev/null 2>&1
fn_print_ok_nl "Sending RCON command to server: \"${rconcommandtosend}\""
fn_script_log_pass "RCON command \"${rconcommandtosend}\" sent to server"
core_exit.sh

View File

@ -24,6 +24,7 @@ cmd_monitor=("m;monitor" "command_monitor.sh" "Check server status and restart i
cmd_skeleton=("sk;skeleton" "command_skeleton.sh" "Create a skeleton directory.")
cmd_sponsor=("s;sponsor" "command_sponsor.sh" "Sponsorship options.")
cmd_send=("sd;send" "command_send.sh" "Send command to game server console.")
cmd_rcon=("rc;rcon" "command_rcon.sh" "Send RCON command to game server.")
# Console servers only.
cmd_console=("c;console" "command_console.sh" "Access server console.")
cmd_debug=("d;debug" "command_debug.sh" "Start server directly in your terminal.")
@ -92,6 +93,11 @@ if [ "${consoleinteract}" == "yes" ]; then
currentopt+=("${cmd_send[@]}")
fi
# RCON command.
# TODO: Add RCON type to all _default.cfg files [Source RCON Protocol / Other Protocols?! / None?!]
# TODO: then add a check in the rcon command to use the appropriate protocol
currentopt+=("${cmd_rcon[@]}")
## Game server exclusive commands.
# FastDL command.

View File

@ -181,6 +181,11 @@ command_send.sh() {
fn_fetch_module
}
command_rcon.sh() {
modulefile="${FUNCNAME[0]}"
fn_fetch_module
}
# Checks
check.sh() {
@ -739,6 +744,11 @@ install_gsquery.sh() {
fn_fetch_module
}
install_rcon.sh() {
modulefile="${FUNCNAME[0]}"
fn_fetch_module
}
install_gslt.sh() {
modulefile="${FUNCNAME[0]}"
fn_fetch_module

116
lgsm/modules/rcon.py Normal file
View File

@ -0,0 +1,116 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# LinuxGSM rcon.py module
# Author: MicLieg
# Contributors: http://linuxgsm.com/contrib
# Website: https://linuxgsm.com
# Description: Allows sending RCON commands to different gameservers.
import argparse
import socket
import struct
import sys
class PacketTypes:
LOGIN = 3
COMMAND = 2
class Rcon:
def __init__(self, arguments):
self.arguments = arguments
self.connection = None
def __enter__(self):
self.connect_to_server()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.connection:
self.connection.close()
@staticmethod
def fatal_error(error_message, error_code):
sys.stderr.write(f'ERROR: {error_code} {error_message}\n')
sys.exit(error_code)
@staticmethod
def exit_success(success_message=''):
sys.stdout.write(f'OK: {success_message}\n')
sys.exit(0)
def connect_to_server(self):
self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connection.settimeout(self.arguments.timeout)
try:
self.connection.connect((self.arguments.address, self.arguments.port))
except socket.timeout:
self.fatal_error('Request timed out', 1)
except Exception as e:
self.fatal_error(f'Unable to connect: {e}', 1)
def send_packet(self, request_id, packet_type, data):
# Packet structure follows the Source RCON Protocol: size, request ID, type, data, two null bytes
packet = (
struct.pack('<l', request_id)
+ struct.pack('<l', packet_type)
+ data.encode('utf8') + b'\x00\x00'
)
try:
self.connection.send(struct.pack('<l', len(packet)) + packet)
except socket.error as e:
self.fatal_error(f'Failed to send packet: {e}', 2)
def receive_packet(self):
try:
response = self.connection.recv(self.arguments.buffer)
return response
except socket.error as e:
self.fatal_error(f'Failed to receive response: {e}', 3)
def login(self):
self.send_packet(1, PacketTypes.LOGIN, self.arguments.password)
response = self.receive_packet()
if response:
size, id_response, type_response = struct.unpack('<l', response[:4]), struct.unpack('<l', response[
4:8]), struct.unpack(
'<l', response[8:12])
if id_response[0] == -1:
self.fatal_error('Login to RCON failed', 4)
else:
self.fatal_error('No response received for login', 4)
def send_command(self):
self.send_packet(2, PacketTypes.COMMAND, self.arguments.command)
response = self.receive_packet()
if response:
response_message = response[12:-2].decode('utf-8') # Stripping trailing null bytes
self.exit_success(str(response_message))
else:
self.fatal_error('No response received for command', 5)
def parse_args():
parser = argparse.ArgumentParser(description='Sends RCON commands to Minecraft servers.')
parser.add_argument('-a', '--address', type=str, required=True, help='The server IP address.')
parser.add_argument('-p', '--port', type=int, required=True, help='The server port.')
parser.add_argument('-P', '--password', type=str, required=True, help='The RCON password.')
parser.add_argument('-c', '--command', type=str, required=True, help='The RCON command to send.')
parser.add_argument('-t', '--timeout', type=int, default=5, help='The timeout for server response.')
parser.add_argument('-b', '--buffer', type=int, default=4096, help='The buffer length for server response.')
return parser.parse_args()
def main():
arguments = parse_args()
with Rcon(arguments) as rcon:
rcon.login()
rcon.send_command()
if __name__ == '__main__':
main()