Start adding RCON part!

This commit is contained in:
Maxim Khomutov 2023-07-26 05:00:49 +03:00
parent 21dd23cb55
commit 719e705bab
7 changed files with 131 additions and 34 deletions

View File

@ -9,3 +9,4 @@ click~=8.1.4
lupa~=2.0 lupa~=2.0
toml~=0.10.2 toml~=0.10.2
colorama~=0.4.6 colorama~=0.4.6
cryptography~=41.0.2

View File

@ -270,6 +270,9 @@ class Core:
tasks = [] tasks = []
# self.udp.start, # self.udp.start,
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive] f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
if config.RCON['enabled']:
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
f_tasks.append(rcon.start)
for task in f_tasks: for task in f_tasks:
tasks.append(asyncio.create_task(task())) tasks.append(asyncio.create_task(task()))
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)

View File

@ -1,17 +1,14 @@
import secrets from typing import Dict
class Config: class Config:
def __init__(self, auth=None, game=None, server=None, options=None, web=None): Auth: Dict[str, object]
self.Auth = auth or {"key": None, "private": True} Game: Dict[str, object]
self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1} Server: Dict[str, object]
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", RCON: Dict[str, object]
"server_ip": "0.0.0.0", "server_port": 30814} Options: Dict[str, object]
self.Options = options or {"language": "en", "encoding": "utf8", "speed_limit": 0, "use_queue": False, WebAPI: Dict[str, object]
"debug": False} enc: str | None
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
"secret_key": secrets.token_hex(16)}
def __repr__(self): def __repr__(self):
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server) return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)
class config (Config): ... class config (Config): ...

View File

@ -12,15 +12,17 @@ import yaml
class Config: class Config:
def __init__(self, auth=None, game=None, server=None, options=None, web=None): def __init__(self, auth=None, game=None, server=None, rcon=None, options=None, web=None):
self.Auth = auth or {"key": None, "private": True} self.Auth = auth or {"key": None, "private": True}
self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1} self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1}
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
"server_ip": "0.0.0.0", "server_port": 30814} "server_ip": "0.0.0.0", "server_port": 30814}
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
"password": secrets.token_hex(16)}
self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False, self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False,
"debug": False, "use_lua": False, "log_chat": True} "debug": False, "use_lua": False, "log_chat": True}
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433, self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
"secret_key": secrets.token_hex(16)} "access_token": secrets.token_hex(16)}
def __repr__(self): def __repr__(self):
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server) return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)

View File

@ -0,0 +1,92 @@
import hashlib
import os
from base64 import b64decode, b64encode
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from core import get_logger
"""
shared key: SHA256 of "password"
<header>: "\x00\x00\x00\x00" (Byte order: Little Endian) - like you use
<iv>: A set of random bytes packed in base64 (New for each message)
-> To server
<- From server
Open TCP connection /
| -> "<iv>:hello" Without header, immediately with AES encryption (shared key)
| *Decrypt and some processes*
| Fail /
| | <- ":E:Bad key" | ":E:Error Message" Without header, without AES encryption
| | tcp.close() # End
| Success /
| | <- "<iv>:hello" with header, with AES encryption
| | (Next, everywhere with header, with AES encryption)
| -> "<iv>:<header>Cs:ver"
| <- "<iv>:<header>Os:KuiToi 0.4.3 | "<iv>:<header>Os:BeamMP 3.2.0"
| # Prints server and they version
| -> "<iv>:<header>Cs:commands"
| <- "<iv>:<header>Os:stop,help,plugins" | "<iv>:<header>Os:SKIP" For an autocomplete; "SKIP" For no autocomplete;
| *Ready to handle commands*
| -> "<iv>:<header>C:help"
| <- "<iv>:<header>O:stop: very cool stop\nhelp: Yayayayoy"
| -> "<iv>:<header>C:...."
| <- "<iv>:<header>O:...."
| -> "<iv>:<header>C:exit"
| tcp.close()
Codes:
* "hello" - Hello message
* "E:error_message" - Send RCON error
* "C:command" - Receive command
* "Cs:" - Receive system command
* "O:output" - Send command output
* "Os:" - Send system output
"""
class RCONSystem:
console = None
def __init__(self, key, host, port):
self.log = get_logger("RCON")
self.key = key
self.host = host
self.port = port
def encrypt(self, message, key):
self.log.debug(f"Encrypt message: {message}")
key = hashlib.sha256(key).digest()
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(message.encode('utf-8')) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
encoded_data = b64encode(encrypted_data)
encoded_iv = b64encode(iv)
return encoded_iv + b":" + encoded_data
def decrypt(self, ciphertext, key):
self.log.debug(f"Dencrypt message: {ciphertext}")
key = hashlib.sha256(key).digest()
encoded_iv, encoded_data = ciphertext.split(":")
iv = b64decode(encoded_iv)
encrypted_data = b64decode(encoded_data)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
return unpadded_data.decode('utf-8')
async def handle_client(self):
pass
async def start(self):
self.log.info("TODO: RCON")
async def stop(self):
pass

View File

@ -1,36 +1,34 @@
class Console(object): from logging import Logger
from typing import AnyStr
def __init__(self, from core import get_logger
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = "Command \"%s\" not found in alias.") -> None: ...
def __getitem__(self, item): ...
@property
def alias(self) -> dict: ...
def add(self, key: str, func: function) -> dict: ...
def log(self, s: str, r='\r') -> None: ...
def write(self, s: str, r='\r') -> None: ...
def __lshift__(self, s: AnyStr) -> None: ...
def logger_hook(self) -> None: ...
def builtins_hook(self) -> None: ...
async def start(self) -> None: ...
class console(object): class RCONSystem:
console = None
def __init__(self, key, host, port):
self.log = get_logger("RCON")
self.key = key
self.host = host
self.port = port
async def start(self): ...
async def stop(self): ...
class console:
rcon: RCONSystem = RCONSystem
@staticmethod @staticmethod
def alias() -> dict: ... def alias() -> dict: ...
@staticmethod @staticmethod
def add_command(key: str, func: function) -> dict: ... def add_command(key: str, func, man: str = None, desc: str = None, custom_completer: dict = None) -> dict: ...
@staticmethod @staticmethod
async def start() -> None: ... async def start() -> None: ...
@staticmethod @staticmethod
def builtins_hook() -> None: ... def builtins_hook() -> None: ...
@staticmethod @staticmethod
def logger_hook() -> None: ... def logger_hook() -> None: ...
@staticmethod @staticmethod
def log(s: str) -> None: ... def log(s: str) -> None: ...
@staticmethod @staticmethod

View File

@ -18,6 +18,7 @@ from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
from prompt_toolkit.patch_stdout import patch_stdout from prompt_toolkit.patch_stdout import patch_stdout
from core import get_logger from core import get_logger
from modules.ConsoleSystem import RCON
class Console: class Console:
@ -45,6 +46,9 @@ class Console:
self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help, self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help,
custom_completer={"help": {"--raw": None}}) custom_completer={"help": {"--raw": None}})
self.completer = NestedCompleter.from_nested_dict(self.__alias) self.completer = NestedCompleter.from_nested_dict(self.__alias)
rcon = RCON
rcon.console = self
self.rcon = rcon
def __debug(self, *x): def __debug(self, *x):
self.__logger.debug(f"{x}") self.__logger.debug(f"{x}")