Skip to content

Commit

Permalink
Implemented proof of concept RCON command
Browse files Browse the repository at this point in the history
  • Loading branch information
MicLieg committed Apr 24, 2024
1 parent ad2f815 commit 212e02e
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 4 deletions.
8 changes: 4 additions & 4 deletions lgsm/modules/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
44 changes: 44 additions & 0 deletions lgsm/modules/command_rcon.sh
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions lgsm/modules/core_getopt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions lgsm/modules/core_modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ command_send.sh() {
fn_fetch_module
}

command_rcon.sh() {
modulefile="${FUNCNAME[0]}"
fn_fetch_module
}

# Checks

check.sh() {
Expand Down Expand Up @@ -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
Expand Down
116 changes: 116 additions & 0 deletions lgsm/modules/rcon.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 212e02e

Please sign in to comment.