From 212e02e16e9de66cd06fee6afd81d06fe170744f Mon Sep 17 00:00:00 2001 From: MicLieg Date: Tue, 6 Feb 2024 00:58:59 +0100 Subject: [PATCH] Implemented proof of concept RCON command --- lgsm/modules/check.sh | 8 +-- lgsm/modules/command_rcon.sh | 44 +++++++++++++ lgsm/modules/core_getopt.sh | 6 ++ lgsm/modules/core_modules.sh | 10 +++ lgsm/modules/rcon.py | 116 +++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 lgsm/modules/command_rcon.sh create mode 100644 lgsm/modules/rcon.py diff --git a/lgsm/modules/check.sh b/lgsm/modules/check.sh index 2a63df3f3e..414a17883e 100644 --- a/lgsm/modules/check.sh +++ b/lgsm/modules/check.sh @@ -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 diff --git a/lgsm/modules/command_rcon.sh b/lgsm/modules/command_rcon.sh new file mode 100644 index 0000000000..1d7078d90a --- /dev/null +++ b/lgsm/modules/command_rcon.sh @@ -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 diff --git a/lgsm/modules/core_getopt.sh b/lgsm/modules/core_getopt.sh index cd3e57cb57..c858d5f22f 100644 --- a/lgsm/modules/core_getopt.sh +++ b/lgsm/modules/core_getopt.sh @@ -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. diff --git a/lgsm/modules/core_modules.sh b/lgsm/modules/core_modules.sh index 6fda8c2ca4..6fe3c1f07b 100644 --- a/lgsm/modules/core_modules.sh +++ b/lgsm/modules/core_modules.sh @@ -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 diff --git a/lgsm/modules/rcon.py b/lgsm/modules/rcon.py new file mode 100644 index 0000000000..789236d09f --- /dev/null +++ b/lgsm/modules/rcon.py @@ -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('