From bd95b6b448d7508d9a890b75312cac36a7063a61 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 23 Aug 2025 19:26:22 +0200 Subject: [PATCH] V6.2.1 Adding main module utils and rehash utils to manager reload/rehash/restart --- core/base.py | 107 +---- core/classes/admin.py | 2 - core/classes/commands.py | 28 +- core/classes/config.py | 30 +- core/classes/protocols/inspircd.py | 25 +- core/classes/protocols/unreal6.py | 229 +++++++--- core/classes/rehash.py | 115 +++++ core/classes/settings.py | 1 + core/classes/user.py | 21 +- core/definition.py | 11 +- core/irc.py | 710 ++++------------------------- core/loader.py | 28 +- core/logs.py | 64 ++- core/module.py | 424 +++++++++++++++++ core/utils.py | 68 ++- mods/clone/utils.py | 2 +- mods/command/mod_command.py | 101 ++-- mods/command/utils.py | 6 + mods/defender/mod_defender.py | 15 +- mods/defender/utils.py | 4 +- mods/votekick/mod_votekick.py | 2 +- mods/votekick/votekick_manager.py | 2 +- version.json | 2 +- 23 files changed, 1085 insertions(+), 912 deletions(-) create mode 100644 core/classes/rehash.py create mode 100644 core/module.py diff --git a/core/base.py b/core/base.py index 012d0e9..d6e89c0 100644 --- a/core/base.py +++ b/core/base.py @@ -4,10 +4,7 @@ import re import json import sys import time -import random import socket -import hashlib -import logging import threading import ipaddress import ast @@ -17,7 +14,6 @@ from types import ModuleType from dataclasses import fields from typing import Any, Optional, TYPE_CHECKING from base64 import b64decode, b64encode -from datetime import datetime, timedelta, timezone from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy.sql import text @@ -34,7 +30,6 @@ class Base: self.Utils = loader.Utils self.logs = loader.Logs - # self.init_log_system() # Demarrer le systeme de log self.check_for_new_version(True) # Verifier si une nouvelle version est disponible # Liste des timers en cours @@ -141,42 +136,6 @@ class Base: except Exception as err: self.logs.error(f'General Error: {err}') - def get_all_modules(self) -> list[str]: - """Get list of all main modules - using this pattern mod_*.py - - Returns: - list[str]: List of module names. - """ - base_path = Path('mods') - return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')] - - def reload_modules_with_dependencies(self, prefix: str = 'mods'): - """ - Reload all modules in sys.modules that start with the given prefix. - Useful for reloading a full package during development. - """ - modules_to_reload = [] - - # Collect target modules - for name, module in sys.modules.items(): - if ( - isinstance(module, ModuleType) - and module is not None - and name.startswith(prefix) - ): - modules_to_reload.append((name, module)) - - # Sort to reload submodules before parent modules - for name, module in sorted(modules_to_reload, key=lambda x: x[0], reverse=True): - try: - if 'mod_' not in name and 'schemas' not in name: - importlib.reload(module) - self.logs.debug(f'[LOAD_MODULE] Module {module} success') - - except Exception as err: - self.logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}') - def create_log(self, log_message: str) -> None: """Enregiste les logs @@ -212,68 +171,6 @@ class Base: return None - def db_isModuleExist(self, module_name:str) -> bool: - """Teste si un module existe déja dans la base de données - - Args: - module_name (str): le non du module a chercher dans la base de données - - Returns: - bool: True si le module existe déja dans la base de données sinon False - """ - query = f"SELECT id FROM {self.Config.TABLE_MODULE} WHERE module_name = :module_name" - mes_donnes = {'module_name': module_name} - results = self.db_execute_query(query, mes_donnes) - - if results.fetchall(): - return True - else: - return False - - def db_record_module(self, user_cmd: str, module_name: str, isdefault: int = 0) -> None: - """Enregistre les modules dans la base de données - - Args: - user_cmd (str): The user who performed the command - module_name (str): The module name - isdefault (int): Is this a default module. Default 0 - """ - - if not self.db_isModuleExist(module_name): - self.logs.debug(f"Le module {module_name} n'existe pas alors ont le créer") - insert_cmd_query = f"INSERT INTO {self.Config.TABLE_MODULE} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)" - mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': isdefault} - self.db_execute_query(insert_cmd_query, mes_donnees) - else: - self.logs.debug(f"Le module {module_name} existe déja dans la base de données") - - return None - - def db_update_module(self, user_cmd: str, module_name: str) -> None: - """Modifie la date et le user qui a rechargé le module - - Args: - user_cmd (str): le user qui a rechargé le module - module_name (str): le module a rechargé - """ - update_cmd_query = f"UPDATE {self.Config.TABLE_MODULE} SET datetime = :datetime, user = :user WHERE module_name = :module_name" - mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'module_name': module_name} - self.db_execute_query(update_cmd_query, mes_donnees) - - return None - - def db_delete_module(self, module_name:str) -> None: - """Supprime les modules de la base de données - - Args: - module_name (str): The module name you want to delete - """ - insert_cmd_query = f"DELETE FROM {self.Config.TABLE_MODULE} WHERE module_name = :module_name" - mes_donnees = {'module_name': module_name} - self.db_execute_query(insert_cmd_query, mes_donnees) - - return None - def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool: """Sync module local parameters with the database if new module then local param will be stored in the database @@ -667,8 +564,8 @@ class Base: self.db_execute_query(table_core_config) if self.install: - self.db_record_module('sys', 'mod_command', 1) - self.db_record_module('sys', 'mod_defender', 1) + self.Loader.ModuleUtils.db_register_module('mod_command', 'sys', True) + self.Loader.ModuleUtils.db_register_module('mod_defender', 'sys', True) self.install = False return None diff --git a/core/classes/admin.py b/core/classes/admin.py index e7ce183..3fa1750 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -1,5 +1,4 @@ from typing import TYPE_CHECKING, Optional -from core.base import Base from core.definition import MAdmin if TYPE_CHECKING: @@ -24,7 +23,6 @@ class Admin: for record in self.UID_ADMIN_DB: if record.uid == new_admin.uid: - # If the admin exist then return False and do not go further self.Logs.debug(f'{record.uid} already exist') return False diff --git a/core/classes/commands.py b/core/classes/commands.py index 64e9e93..bb0187e 100644 --- a/core/classes/commands.py +++ b/core/classes/commands.py @@ -9,6 +9,7 @@ class Command: DB_COMMANDS: list['MCommand'] = [] def __init__(self, loader: 'Loader'): + self.Loader = loader self.Base = loader.Base def build(self, new_command_obj: MCommand) -> bool: @@ -26,7 +27,7 @@ class Command: return True return False - + def get_command(self, command_name: str, module_name: str) -> Optional[MCommand]: for command in self.DB_COMMANDS: @@ -56,4 +57,27 @@ class Command: if cmd.command_level <= level: new_list.append(cmd) - return new_list \ No newline at end of file + return new_list + + def is_client_allowed_to_run_command(self, nickname: str, command_name: str) -> bool: + admin = self.Loader.Admin.get_admin(nickname) + admin_level = admin.level if admin else 0 + commands = self.get_commands_by_level(admin_level) + + if command_name in [command.command_name for command in commands]: + return True + + return False + + def is_command_exist(self, command_name: str) -> bool: + """Check if the command name exist + + Args: + command_name (str): The command name you want to check + + Returns: + bool: True if the command exist + """ + if command_name.lower() in [command.command_name.lower() for command in self.get_ordered_commands()]: + return True + return False diff --git a/core/classes/config.py b/core/classes/config.py index ca2f7ce..724ee2a 100644 --- a/core/classes/config.py +++ b/core/classes/config.py @@ -1,23 +1,32 @@ from json import load from sys import exit from os import sep -from typing import Union +from typing import Any, Optional, Union, TYPE_CHECKING from core.definition import MConfig -from logging import Logger +if TYPE_CHECKING: + from core.loader import Loader class Configuration: - def __init__(self, logs: Logger) -> None: - self.Logs = logs - self.ConfigObject: MConfig = self.__load_service_configuration() + def __init__(self, loader: 'Loader') -> None: + + self.Loader = loader + self.Logs = loader.Logs + self._config_model: MConfig = self.__load_service_configuration() + loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL) + loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL) + loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD) return None - def __load_json_service_configuration(self): + def get_config_model(self) -> MConfig: + return self._config_model + + def __load_json_service_configuration(self) -> Optional[dict[str, Any]]: try: conf_filename = f'config{sep}configuration.json' with open(conf_filename, 'r') as configuration_data: - configuration:dict[str, Union[str, int, list, dict]] = load(configuration_data) + configuration: dict[str, Union[str, int, list, dict]] = load(configuration_data) return configuration @@ -48,11 +57,8 @@ class Configuration: import_config.pop(json_conf, None) self.Logs.warning(f"[!] The key {json_conf} is not expected, it has been removed from the system ! please remove it from configuration.json file [!]") - ConfigObject: MConfig = MConfig( - **import_config - ) - - return ConfigObject + self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!") + return MConfig(**import_config) except TypeError as te: self.Logs.error(te) \ No newline at end of file diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index 67bc57e..33debaa 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -59,8 +59,8 @@ class Inspircd: """ try: batch_size = self.__Config.BATCH_SIZE - User_from = self.__Irc.User.get_User(nick_from) - User_to = self.__Irc.User.get_User(nick_to) if nick_to is None else None + User_from = self.__Irc.User.get_user(nick_from) + User_to = self.__Irc.User.get_user(nick_to) if nick_to is None else None if User_from is None: self.__Logs.error(f"The sender nickname [{nick_from}] do not exist") @@ -88,8 +88,8 @@ class Inspircd: """ try: batch_size = self.__Config.BATCH_SIZE - User_from = self.__Irc.User.get_User(nick_from) - User_to = self.__Irc.User.get_User(nick_to) + User_from = self.__Irc.User.get_user(nick_from) + User_to = self.__Irc.User.get_user(nick_to) if User_from is None or User_to is None: self.__Logs.error(f"The sender [{nick_from}] or the Reciever [{nick_to}] do not exist") @@ -188,17 +188,13 @@ class Inspircd: uidornickname (str): The UID or the Nickname reason (str): The reason for the quit """ - user_obj = self.__Irc.User.get_User(uidornickname=uid) - clone_obj = self.__Irc.Clone.get_clone(uidornickname=uid) + user_obj = self.__Irc.User.get_user(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) if not user_obj is None: self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log) self.__Irc.User.delete(user_obj.uid) - if not clone_obj is None: - self.__Irc.Clone.delete(clone_obj.uid) - if not reputationObj is None: self.__Irc.Reputation.delete(reputationObj.uid) @@ -255,7 +251,7 @@ class Inspircd: print_log (bool, optional): Write logs. Defaults to True. """ - userObj = self.__Irc.User.get_User(uidornickname) + userObj = self.__Irc.User.get_user(uidornickname) passwordChannel = password if not password is None else '' if userObj is None: @@ -280,7 +276,7 @@ class Inspircd: print_log (bool, optional): Write logs. Defaults to True. """ - userObj = self.__Irc.User.get_User(uidornickname) + userObj = self.__Irc.User.get_user(uidornickname) if userObj is None: self.__Logs.error(f"The user [{uidornickname}] is not valid") @@ -311,7 +307,7 @@ class Inspircd: try: # [':adator_', 'UMODE2', '-iwx'] - userObj = self.__Irc.User.get_User(str(serverMsg[0]).lstrip(':')) + userObj = self.__Irc.User.get_user(str(serverMsg[0]).lstrip(':')) userMode = serverMsg[2] if userObj is None: # If user is not created @@ -346,7 +342,6 @@ class Inspircd: self.__Irc.Channel.delete_user_from_all_channel(uid_who_quit) self.__Irc.User.delete(uid_who_quit) self.__Irc.Reputation.delete(uid_who_quit) - self.__Irc.Clone.delete(uid_who_quit) return None @@ -655,7 +650,7 @@ class Inspircd: """ try: # ['@label=0073', ':0014E7P06', 'VERSION', 'PyDefender'] - getUser = self.__Irc.User.get_User(self.__Utils.clean_uid(serverMsg[1])) + getUser = self.__Irc.User.get_user(self.__Utils.clean_uid(serverMsg[1])) if getUser is None: return None @@ -663,7 +658,7 @@ class Inspircd: response_351 = f"{self.__Config.SERVICE_NAME.capitalize()}-{self.__Config.CURRENT_VERSION} {self.__Config.SERVICE_HOST} {self.name}" self.send2socket(f':{self.__Config.SERVICE_HOST} 351 {getUser.nickname} {response_351}') - modules = self.__Base.get_all_modules() + modules = self.__Irc.ModuleUtils.get_all_available_modules() response_005 = ' | '.join(modules) self.send2socket(f':{self.__Config.SERVICE_HOST} 005 {getUser.nickname} {response_005} are supported by this server') diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index ddae34b..22cbf4c 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -5,6 +5,7 @@ from ssl import SSLEOFError, SSLError if TYPE_CHECKING: from core.irc import Irc + from core.definition import MClient class Unrealircd6: @@ -23,7 +24,8 @@ class Unrealircd6: 'EOS', 'PRIVMSG', 'MODE', 'UMODE2', 'VERSION', 'REPUTATION', 'SVS2MODE', 'SLOG', 'NICK', 'PART', 'PONG', - 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO'} + 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO', + '006', '007', '018'} self.__Logs.info(f"** Loading protocol [{__name__}]") @@ -44,6 +46,40 @@ class Unrealircd6: return (-1, None) + def parse_server_msg(self, server_msg: list[str]) -> Optional[str]: + """Parse the server message and return the command + + Args: + server_msg (list[str]): The Original server message >> + + Returns: + Union[str, None]: Return the command protocol name + """ + protocol_exception = ['PING', 'SERVER', 'PROTOCTL'] + increment = 0 + server_msg_copy = server_msg.copy() + first_index = 0 + second_index = 0 + for index, element in enumerate(server_msg_copy): + # Handle the protocol exceptions ex. ping, server .... + if element in protocol_exception and index == 0: + return element + + if element.startswith(':'): + increment += 1 + first_index = index + 1 if increment == 1 else first_index + second_index = index if increment == 2 else second_index + + second_index = len(server_msg_copy) if second_index == 0 else second_index + + parsed_msg = server_msg_copy[first_index:second_index] + + for cmd in parsed_msg: + if cmd in self.known_protocol: + return cmd + + return None + def send2socket(self, message: str, print_log: bool = True) -> None: """Envoit les commandes à envoyer au serveur. @@ -84,8 +120,8 @@ class Unrealircd6: """ try: batch_size = self.__Config.BATCH_SIZE - User_from = self.__Irc.User.get_User(nick_from) - User_to = self.__Irc.User.get_User(nick_to) if not nick_to is None else None + User_from = self.__Irc.User.get_user(nick_from) + User_to = self.__Irc.User.get_user(nick_to) if not nick_to is None else None if User_from is None: self.__Logs.error(f"The sender nickname [{nick_from}] do not exist") @@ -115,8 +151,8 @@ class Unrealircd6: """ try: batch_size = self.__Config.BATCH_SIZE - User_from = self.__Irc.User.get_User(nick_from) - User_to = self.__Irc.User.get_User(nick_to) + User_from = self.__Irc.User.get_user(nick_from) + User_to = self.__Irc.User.get_user(nick_to) if User_from is None or User_to is None: self.__Logs.error(f"The sender [{nick_from}] or the Reciever [{nick_to}] do not exist") @@ -129,40 +165,6 @@ class Unrealircd6: except Exception as err: self.__Logs.error(f"General Error: {err}") - def parse_server_msg(self, server_msg: list[str]) -> Optional[str]: - """Parse the server message and return the command - - Args: - server_msg (list[str]): The Original server message >> - - Returns: - Union[str, None]: Return the command protocol name - """ - protocol_exception = ['PING', 'SERVER', 'PROTOCTL'] - increment = 0 - server_msg_copy = server_msg.copy() - first_index = 0 - second_index = 0 - for index, element in enumerate(server_msg_copy): - # Handle the protocol exceptions ex. ping, server .... - if element in protocol_exception and index == 0: - return element - - if element.startswith(':'): - increment += 1 - first_index = index + 1 if increment == 1 else first_index - second_index = index if increment == 2 else second_index - - second_index = len(server_msg_copy) if second_index == 0 else second_index - - parsed_msg = server_msg_copy[first_index:second_index] - - for cmd in parsed_msg: - if cmd in self.known_protocol: - return cmd - - return None - def send_link(self): """Créer le link et envoyer les informations nécessaires pour la connexion au serveur. @@ -217,7 +219,7 @@ class Unrealircd6: """ self.send2socket(f":{self.__Config.SERVICE_NICKNAME} NICK {newnickname}") - userObj = self.__Irc.User.get_User(self.__Config.SERVICE_NICKNAME) + userObj = self.__Irc.User.get_user(self.__Config.SERVICE_NICKNAME) self.__Irc.User.update_nickname(userObj.uid, newnickname) return None @@ -275,7 +277,7 @@ class Unrealircd6: """ try: - userObj = self.__Irc.User.get_User(uidornickname=nick_to_sapart) + userObj = self.__Irc.User.get_user(uidornickname=nick_to_sapart) chanObj = self.__Irc.Channel.get_channel(channel_name) service_uid = self.__Config.SERVICE_ID @@ -299,7 +301,7 @@ class Unrealircd6: """ try: - userObj = self.__Irc.User.get_User(uidornickname=nick_to_sajoin) + userObj = self.__Irc.User.get_user(uidornickname=nick_to_sajoin) chanObj = self.__Irc.Channel.get_channel(channel_name) service_uid = self.__Config.SERVICE_ID @@ -327,25 +329,92 @@ class Unrealircd6: except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def send_svs_mode(self, nickname: str, user_mode: str) -> None: - try: + def send_svspart(self, nick_to_part: str, channels: list[str], reason: str) -> None: + user_obj = self.__Irc.User.get_user(nick_to_part) - userObj = self.__Irc.User.get_User(uidornickname=nickname) + if user_obj is None: + self.__Logs.debug(f"[SVSPART] The nickname {nick_to_part} do not exist!") + return None + + channels_list = ','.join([channel for channel in channels if self.__Irc.Channel.is_valid_channel(channel)]) + service_id = self.__Config.SERVICE_ID + self.send2socket(f':{service_id} SVSPART {user_obj.nickname} {channels_list} {reason}') + return None + + def send_svsjoin(self, nick_to_part: str, channels: list[str], keys: list[str]) -> None: + user_obj = self.__Irc.User.get_user(nick_to_part) + + if user_obj is None: + self.__Logs.debug(f"[SVSJOIN] The nickname {nick_to_part} do not exist!") + return None + + channels_list = ','.join([channel for channel in channels if self.__Irc.Channel.is_valid_channel(channel)]) + keys_list = ','.join([key for key in keys]) + service_id = self.__Config.SERVICE_ID + self.send2socket(f':{service_id} SVSJOIN {user_obj.nickname} {channels_list} {keys_list}') + return None + + def send_svsmode(self, nickname: str, user_mode: str) -> None: + try: + user_obj = self.__Irc.User.get_user(uidornickname=nickname) service_uid = self.__Config.SERVICE_ID - if userObj is None: - # User not exist: leave + if user_obj is None: return None self.send2socket(f':{service_uid} SVSMODE {nickname} {user_mode}') # Update new mode - self.__Irc.User.update_mode(userObj.uid, user_mode) + self.__Irc.User.update_mode(user_obj.uid, user_mode) return None except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") + def send_svs2mode(self, nickname: str, user_mode: str) -> None: + try: + user_obj = self.__Irc.User.get_user(uidornickname=nickname) + service_uid = self.__Config.SERVICE_ID + + if user_obj is None: + return None + + self.send2socket(f':{service_uid} SVS2MODE {nickname} {user_mode}') + + # Update new mode + self.__Irc.User.update_mode(user_obj.uid, user_mode) + + return None + except Exception as err: + self.__Logs.error(f"{__name__} - General Error: {err}") + + def send_svslogin(self, client_uid: str, user_account: str) -> None: + """Log a client into his account. + + Args: + client_uid (str): Client UID + user_account (str): The account of the user + """ + try: + self.send2socket(f":{self.__Irc.Config.SERVEUR_LINK} SVSLOGIN {self.__Settings.MAIN_SERVER_HOSTNAME} {client_uid} {user_account}") + except Exception as err: + self.__Irc.Logs.error(f'General Error: {err}') + + def send_svslogout(self, client_obj: 'MClient') -> None: + """Logout a client from his account + + Args: + client_uid (str): The Client UID + """ + try: + c_uid = client_obj.uid + c_nickname = client_obj.nickname + self.send2socket(f":{self.__Irc.Config.SERVEUR_LINK} SVSLOGIN {self.__Settings.MAIN_SERVER_HOSTNAME} {c_uid} 0") + self.send_svs2mode(c_nickname, '-r') + + except Exception as err: + self.__Irc.Logs.error(f'General Error: {err}') + def send_quit(self, uid: str, reason: str, print_log: True) -> None: """Send quit message - Delete uid from User object @@ -355,7 +424,7 @@ class Unrealircd6: uidornickname (str): The UID or the Nickname reason (str): The reason for the quit """ - user_obj = self.__Irc.User.get_User(uidornickname=uid) + user_obj = self.__Irc.User.get_user(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) if not user_obj is None: @@ -418,7 +487,7 @@ class Unrealircd6: print_log (bool, optional): Write logs. Defaults to True. """ - userObj = self.__Irc.User.get_User(uidornickname) + userObj = self.__Irc.User.get_user(uidornickname) passwordChannel = password if not password is None else '' if userObj is None: @@ -458,7 +527,7 @@ class Unrealircd6: print_log (bool, optional): Write logs. Defaults to True. """ - userObj = self.__Irc.User.get_User(uidornickname) + userObj = self.__Irc.User.get_user(uidornickname) if userObj is None: self.__Logs.error(f"The user [{uidornickname}] is not valid") @@ -506,7 +575,7 @@ class Unrealircd6: uid_user_to_edit = serverMsg[2] umode = serverMsg[3] - userObj = self.__Irc.User.get_User(uid_user_to_edit) + userObj = self.__Irc.User.get_user(uid_user_to_edit) if userObj is None: return None @@ -540,7 +609,7 @@ class Unrealircd6: try: # [':adator_', 'UMODE2', '-iwx'] - userObj = self.__Irc.User.get_User(str(serverMsg[0]).lstrip(':')) + userObj = self.__Irc.User.get_user(str(serverMsg[0]).lstrip(':')) userMode = serverMsg[2] if userObj is None: # If user is not created @@ -794,8 +863,8 @@ class Unrealircd6: self.__Config.DEFENDER_INIT = 0 # Send EOF to other modules - for classe_name, classe_object in self.__Irc.loaded_classes.items(): - classe_object.cmd(server_msg_copy) + for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): + module.class_instance.cmd(server_msg_copy) return None except IndexError as ie: @@ -907,27 +976,14 @@ class Unrealircd6: """ try: srv_msg = serverMsg.copy() - cmd = serverMsg.copy() # Supprimer la premiere valeur si MTAGS activé if cmd[0].startswith('@'): cmd.pop(0) - # Hide auth logs - if len(cmd) == 7: - if cmd[2] == 'PRIVMSG' and cmd[4] == ':auth': - data_copy = cmd.copy() - data_copy[6] = '**********' - self.__Logs.debug(f">> {data_copy}") - else: - self.__Logs.debug(f">> {cmd}") - else: - self.__Logs.debug(f">> {cmd}") - get_uid_or_nickname = str(cmd[0].replace(':','')) user_trigger = self.__Irc.User.get_nickname(get_uid_or_nickname) dnickname = self.__Config.SERVICE_NICKNAME - pattern = fr'(:\{self.__Config.SERVICE_PREFIX})(.*)$' hcmds = search(pattern, ' '.join(cmd)) # va matcher avec tout les caractéres aprés le . @@ -936,7 +992,7 @@ class Unrealircd6: convert_to_string = ' '.join(liste_des_commandes) arg = convert_to_string.split() arg.remove(f':{self.__Config.SERVICE_PREFIX}') - if not arg[0].lower() in self.__Irc.module_commands_list: + if not self.__Irc.Commands.is_command_exist(arg[0]): self.__Logs.debug(f"This command {arg[0]} is not available") self.send_notice( nick_from=self.__Config.SERVICE_NICKNAME, @@ -945,6 +1001,15 @@ class Unrealircd6: ) return None + # if not arg[0].lower() in self.__Irc.module_commands_list: + # self.__Logs.debug(f"This command {arg[0]} is not available") + # self.send_notice( + # nick_from=self.__Config.SERVICE_NICKNAME, + # nick_to=user_trigger, + # msg=f"This command [{self.__Config.COLORS.bold}{arg[0]}{self.__Config.COLORS.bold}] is not available" + # ) + # return None + cmd_to_send = convert_to_string.replace(':','') self.__Base.log_cmd(user_trigger, cmd_to_send) @@ -963,21 +1028,25 @@ class Unrealircd6: # Réponse a un CTCP VERSION if arg[0] == '\x01VERSION\x01': self.on_version(srv_msg) - return False + return None # Réponse a un TIME if arg[0] == '\x01TIME\x01': self.on_time(srv_msg) - return False + return None # Réponse a un PING if arg[0] == '\x01PING': self.on_ping(srv_msg) - return False + return None - if not arg[0].lower() in self.__Irc.module_commands_list: + if not self.__Irc.Commands.is_command_exist(arg[0]): self.__Logs.debug(f"This command {arg[0]} sent by {user_trigger} is not available") - return False + return None + + # if not arg[0].lower() in self.__Irc.module_commands_list: + # self.__Logs.debug(f"This command {arg[0]} sent by {user_trigger} is not available") + # return False cmd_to_send = convert_to_string.replace(':','') self.__Base.log_cmd(user_trigger, cmd_to_send) @@ -1011,6 +1080,14 @@ class Unrealircd6: except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") + def on_server(self, serverMsg: list[str]) -> None: + try: + # ['SERVER', 'irc.local.org', '1', ':U6100-Fhn6OoE-001', 'Local', 'Server'] + sCopy = serverMsg.copy() + self.__Irc.Settings.MAIN_SERVER_HOSTNAME = sCopy[1] + except Exception as err: + self.__Irc.Logs.error(f'General Error: {err}') + def on_version(self, serverMsg: list[str]) -> None: """Sending Server Version to the server @@ -1105,7 +1182,7 @@ class Unrealircd6: if '@' in list(serverMsg_copy[0])[0]: serverMsg_copy.pop(0) - getUser = self.__Irc.User.get_User(self.__Utils.clean_uid(serverMsg_copy[0])) + getUser = self.__Irc.User.get_user(self.__Utils.clean_uid(serverMsg_copy[0])) if getUser is None: return None @@ -1113,7 +1190,7 @@ class Unrealircd6: response_351 = f"{self.__Config.SERVICE_NAME.capitalize()}-{self.__Config.CURRENT_VERSION} {self.__Config.SERVICE_HOST} {self.name}" self.send2socket(f':{self.__Config.SERVICE_HOST} 351 {getUser.nickname} {response_351}') - modules = self.__Base.get_all_modules() + modules = self.__Irc.ModuleUtils.get_all_available_modules() response_005 = ' | '.join(modules) self.send2socket(f':{self.__Config.SERVICE_HOST} 005 {getUser.nickname} {response_005} are supported by this server') diff --git a/core/classes/rehash.py b/core/classes/rehash.py new file mode 100644 index 0000000..3eeb628 --- /dev/null +++ b/core/classes/rehash.py @@ -0,0 +1,115 @@ +import importlib +import sys +import time +from typing import TYPE_CHECKING +import socket +from core.classes.protocol import Protocol + +if TYPE_CHECKING: + from core.irc import Irc + +# Modules impacted by rehashing! +REHASH_MODULES = [ + 'core.definition', + 'core.utils', + 'core.classes.config', + 'core.base', + 'core.classes.commands', + 'core.classes.protocols.unreal6', + 'core.classes.protocols.inspircd', + 'core.classes.protocol' +] + + +def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -> None: + + # reload modules. + for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): + uplink.ModuleUtils.unload_one_module(uplink, module.module_name) + + uplink.ModuleUtils.model_clear() # Clear loaded modules. + uplink.User.UID_DB.clear() # Clear User Object + uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object + uplink.Client.CLIENT_DB.clear() # Clear Client object + uplink.Base.garbage_collector_thread() + + # Reload configuration + uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model() + uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader) + uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol + uplink.Logs.debug(f'[{uplink.Config.SERVICE_NICKNAME} RESTART]: Reloading configuration!') + + uplink.Protocol.send_squit(server_id=uplink.Config.SERVEUR_ID, server_link=uplink.Config.SERVEUR_LINK, reason="Defender Power off") + + uplink.Logs.debug('Restarting Defender ...') + uplink.IrcSocket.shutdown(socket.SHUT_RDWR) + uplink.IrcSocket.close() + + while uplink.IrcSocket.fileno() != -1: + time.sleep(0.5) + uplink.Logs.warning('-- Waiting for socket to close ...') + + uplink.init_service_user() + uplink.Utils.create_socket(uplink) + uplink.Protocol.send_link() + uplink.join_saved_channels() + uplink.ModuleUtils.db_load_all_existing_modules(uplink) + uplink.Config.DEFENDER_RESTART = 0 + +def rehash_service(uplink: 'Irc', nickname: str) -> None: + need_a_restart = ["SERVEUR_ID"] + uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS) + restart_flag = False + config_model_bakcup = uplink.Config + mods = REHASH_MODULES + for mod in mods: + importlib.reload(sys.modules[mod]) + uplink.Protocol.send_priv_msg( + nick_from=uplink.Config.SERVICE_NICKNAME, + msg=f'[REHASH] Module [{mod}] reloaded', + channel=uplink.Config.SERVICE_CHANLOG + ) + + uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model() + uplink.Config.HSID = config_model_bakcup.HSID + uplink.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT + uplink.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART + uplink.Config.SSL_VERSION = config_model_bakcup.SSL_VERSION + uplink.Config.CURRENT_VERSION = config_model_bakcup.CURRENT_VERSION + uplink.Config.LATEST_VERSION = config_model_bakcup.LATEST_VERSION + + conf_bkp_dict: dict = config_model_bakcup.to_dict() + config_dict: dict = uplink.Config.to_dict() + + for key, value in conf_bkp_dict.items(): + if config_dict[key] != value and key != 'COLORS': + uplink.Protocol.send_priv_msg( + nick_from=uplink.Config.SERVICE_NICKNAME, + msg=f'[{key}]: {value} ==> {config_dict[key]}', + channel=uplink.Config.SERVICE_CHANLOG + ) + if key in need_a_restart: + restart_flag = True + + if config_model_bakcup.SERVICE_NICKNAME != uplink.Config.SERVICE_NICKNAME: + uplink.Protocol.send_set_nick(uplink.Config.SERVICE_NICKNAME) + + if restart_flag: + uplink.Config.SERVEUR_ID = config_model_bakcup.SERVEUR_ID + uplink.Protocol.send_priv_msg( + nick_from=uplink.Config.SERVICE_NICKNAME, + channel=uplink.Config.SERVICE_CHANLOG, + msg='You need to restart defender !') + + # Reload Main Commands Module + uplink.Commands = uplink.Loader.CommandModule.Command(uplink.Loader) + uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('db_commands') + + uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader) + uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol + + # Reload Service modules + for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): + uplink.ModuleUtils.reload_one_module(uplink, module.module_name, nickname) + + return None \ No newline at end of file diff --git a/core/classes/settings.py b/core/classes/settings.py index 57d5d13..4685141 100644 --- a/core/classes/settings.py +++ b/core/classes/settings.py @@ -18,6 +18,7 @@ class Settings: CONSOLE: bool = False + MAIN_SERVER_HOSTNAME: str = None PROTOCTL_USER_MODES: list[str] = [] PROTOCTL_PREFIX: list[str] = [] diff --git a/core/classes/user.py b/core/classes/user.py index 379a851..b8fdfc4 100644 --- a/core/classes/user.py +++ b/core/classes/user.py @@ -25,8 +25,7 @@ class User: bool: True if inserted """ - user_obj = self.get_User(new_user.uid) - + user_obj = self.get_user(new_user.uid) if not user_obj is None: # User already created return False return False @@ -45,7 +44,7 @@ class User: Returns: bool: True if updated """ - user_obj = self.get_User(uidornickname=uid) + user_obj = self.get_user(uidornickname=uid) if user_obj is None: return False @@ -65,7 +64,7 @@ class User: bool: True if user mode has been updaed """ response = True - user_obj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_user(uidornickname=uidornickname) if user_obj is None: return False @@ -107,7 +106,7 @@ class User: bool: True if deleted """ - user_obj = self.get_User(uidornickname=uid) + user_obj = self.get_user(uidornickname=uid) if user_obj is None: return False @@ -116,7 +115,7 @@ class User: return True - def get_User(self, uidornickname: str) -> Optional['MUser']: + def get_user(self, uidornickname: str) -> Optional['MUser']: """Get The User Object model Args: @@ -143,7 +142,7 @@ class User: str|None: Return the UID """ - user_obj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_user(uidornickname=uidornickname) if user_obj is None: return None @@ -159,7 +158,7 @@ class User: Returns: str|None: the nickname """ - user_obj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_user(uidornickname=uidornickname) if user_obj is None: return None @@ -175,7 +174,7 @@ class User: Returns: Union[dict[str, any], None]: User Object as a dictionary or None """ - user_obj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_user(uidornickname=uidornickname) if user_obj is None: return None @@ -191,7 +190,7 @@ class User: Returns: bool: True if exist """ - user_obj = self.get_User(uidornickname=uidornikname) + user_obj = self.get_user(uidornickname=uidornikname) if user_obj is None: return False @@ -226,7 +225,7 @@ class User: int: How long in minutes has the user been connected? """ - get_user = self.get_User(uidornickname) + get_user = self.get_user(uidornickname) if get_user is None: return 0 diff --git a/core/definition.py b/core/definition.py index 0398c50..28cacd1 100644 --- a/core/definition.py +++ b/core/definition.py @@ -1,7 +1,7 @@ from datetime import datetime from json import dumps from dataclasses import dataclass, field, asdict, fields -from typing import Literal, Any +from typing import Literal, Any, Optional from os import sep @dataclass @@ -246,6 +246,9 @@ class MConfig(MainModel): DEBUG_LEVEL:Literal[10, 20, 30, 40, 50] = 20 """Logs level: DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50. (default: 20)""" + DEBUG_HARD: bool = False + """Adding filename, function name and the line number to the logs. Default False""" + LOGGING_NAME: str = "defender" """The name of the Logging instance""" @@ -326,3 +329,9 @@ class MCommand(MainModel): command_name: str = None description: str = None command_level: int = 0 + +@dataclass +class MModule(MainModel): + module_name: str = None + class_name: str = None + class_instance: Optional[Any] = None \ No newline at end of file diff --git a/core/irc.py b/core/irc.py index 1c9c451..7b187b1 100644 --- a/core/irc.py +++ b/core/irc.py @@ -1,6 +1,5 @@ import sys import socket -import threading import ssl import re import importlib @@ -9,6 +8,7 @@ import traceback from ssl import SSLSocket from datetime import datetime, timedelta from typing import Optional, Union +from core.classes import rehash from core.loader import Loader from core.classes.protocol import Protocol from core.classes.commands import Command @@ -74,6 +74,9 @@ class Irc: # Use Reputation Instance self.Reputation = self.Loader.Reputation + # Use Module Utils + self.ModuleUtils = self.Loader.ModuleUtils + self.autolimit_started: bool = False """This variable is to make sure the thread is not running""" @@ -83,19 +86,10 @@ class Irc: # Define first IP connexion self.first_connexion_ip: str = None - # Define the dict that will contain all loaded modules - self.loaded_classes:dict[str, 'Irc'] = {} - # Load Commands Utils self.Commands = self.Loader.Commands """Command utils""" - # Global full module commands that contains level, module name, commands and description - self.module_commands: dict[int, dict[str, dict[str, str]]] = {} - - # Global command list contains only the commands - self.module_commands_list: list[str] = [] - self.build_command(0, 'core', 'help', 'This provide the help') self.build_command(0, 'core', 'auth', 'Login to the IRC Service') self.build_command(0, 'core', 'copyright', 'Give some information about the IRC Service') @@ -125,9 +119,8 @@ class Irc: self.build_command(4, 'core', 'rehash', 'Reload the configuration file without restarting') self.build_command(4, 'core', 'raw', 'Send a raw command directly to the IRC server') - # Define the IrcSocket object - self.IrcSocket: Union[socket.socket, SSLSocket] = None + self.IrcSocket: Optional[Union[socket.socket, SSLSocket]] = None self.__create_table() self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, )) @@ -135,7 +128,7 @@ class Irc: ############################################## # CONNEXION IRC # ############################################## - def init_irc(self, ircInstance:'Irc') -> None: + def init_irc(self, ircInstance: 'Irc') -> None: """Create a socket and connect to irc server Args: @@ -143,7 +136,7 @@ class Irc: """ try: self.init_service_user() - self.__create_socket() + self.Utils.create_socket(ircInstance) self.__connect_to_irc(ircInstance) except AssertionError as ae: @@ -161,90 +154,23 @@ class Irc: )) return None - def __create_socket(self) -> None: - """Create a socket to connect SSL or Normal connection - """ - try: - soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) - connexion_information = (self.Config.SERVEUR_IP, self.Config.SERVEUR_PORT) - - if self.Config.SERVEUR_SSL: - # Créer un object ssl - ssl_context = self.__ssl_context() - ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=self.Config.SERVEUR_HOSTNAME) - ssl_connexion.connect(connexion_information) - self.IrcSocket:SSLSocket = ssl_connexion - self.Config.SSL_VERSION = self.IrcSocket.version() - self.Logs.info(f"-- Connexion en mode SSL : Version = {self.Config.SSL_VERSION}") - else: - soc.connect(connexion_information) - self.IrcSocket:socket.socket = soc - self.Logs.info("-- Connexion en mode normal") - - return None - - except ssl.SSLEOFError as soe: - self.Logs.critical(f"SSLEOFError: {soe} - {soc.fileno()}") - except ssl.SSLError as se: - self.Logs.critical(f"SSLError: {se} - {soc.fileno()}") - except OSError as oe: - self.Logs.critical(f"OSError: {oe} - {soc.fileno()}") - if 'connection refused' in str(oe).lower(): - sys.exit(oe) - if soc.fileno() == -1: - sys.exit(soc.fileno()) - - except AttributeError as ae: - self.Logs.critical(f"AttributeError: {ae} - {soc.fileno()}") - - def __ssl_context(self) -> ssl.SSLContext: - - ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - ctx.check_hostname = False - ctx.verify_mode = ssl.CERT_NONE - - self.Logs.debug(f'-- SSLContext initiated with verified mode {ctx.verify_mode}') - - return ctx - def __connect_to_irc(self, ircInstance: 'Irc') -> None: try: - self.init_service_user() - self.ircObject = ircInstance # créer une copie de l'instance Irc + self.ircObject = ircInstance # créer une copie de l'instance Irc self.Protocol = Protocol( protocol=self.Config.SERVEUR_PROTOCOL, ircInstance=self.ircObject ).Protocol - self.Protocol.send_link() # Etablir le link en fonction du protocol choisi - self.signal = True # Une variable pour initier la boucle infinie - self.__join_saved_channels() # Join existing channels - self.load_existing_modules() # Charger les modules existant dans la base de données + self.Protocol.send_link() # Etablir le link en fonction du protocol choisi + self.signal = True # Une variable pour initier la boucle infinie + self.join_saved_channels() # Join existing channels + self.ModuleUtils.db_load_all_existing_modules(self) while self.signal: try: if self.Config.DEFENDER_RESTART == 1: - self.Logs.debug('Restarting Defender ...') - self.IrcSocket.shutdown(socket.SHUT_RDWR) - self.IrcSocket.close() - - while self.IrcSocket.fileno() != -1: - time.sleep(0.5) - self.Logs.warning('--* Waiting for socket to close ...') - - # Reload configuration - self.Loader.Logs = self.Loader.LoggingModule.ServiceLogging().get_logger() - self.Logs.debug('Reloading configuration') - self.Config = self.Loader.ConfModule.Configuration(self.Logs).ConfigObject - self.Base = self.Loader.BaseModule.Base(self.Loader) - self.Protocol = Protocol(self.Config.SERVEUR_PROTOCOL, ircInstance).Protocol - - self.init_service_user() - self.__create_socket() - self.Protocol.send_link() - self.__join_saved_channels() - self.load_existing_modules() - self.Config.DEFENDER_RESTART = 0 + rehash.restart_service(self.ircObject) # 4072 max what the socket can grab buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) @@ -273,7 +199,7 @@ class Irc: except OSError as oe: self.Logs.error(f"SSLError __connect_to_irc: {oe} - {data}") except (socket.error, ConnectionResetError): - print("Connexion reset") + self.Logs.debug("Connexion reset") self.IrcSocket.shutdown(socket.SHUT_RDWR) self.IrcSocket.close() @@ -289,15 +215,11 @@ class Irc: except AttributeError as atte: self.Logs.critical(f"AttributeError: {atte}") except Exception as e: - self.Logs.critical(f"General Error: {e}") - self.Logs.critical(traceback.format_exc()) + self.Logs.critical(f"General Error: {e}", exc_info=True) - def __join_saved_channels(self) -> None: + def join_saved_channels(self) -> None: """## Joining saved channels""" - core_table = self.Config.TABLE_CHANNEL - - query = f'''SELECT distinct channel_name FROM {core_table}''' - exec_query = self.Base.db_execute_query(query) + exec_query = self.Base.db_execute_query(f'SELECT distinct channel_name FROM {self.Config.TABLE_CHANNEL}') result_query = exec_query.fetchall() if result_query: @@ -307,7 +229,6 @@ class Irc: def send_response(self, responses:list[bytes]) -> None: try: - # print(responses) for data in responses: response = data.decode(self.CHARSET[0]).split() self.cmd(response) @@ -346,9 +267,6 @@ class Irc: command_name (str): The command name command_description (str): The description of the command """ - self.module_commands.setdefault(level, {}).setdefault(module_name, {}).update({command_name: command_description}) - self.module_commands_list.append(command_name) - # Build Model. self.Commands.build(self.Loader.Definition.MCommand(module_name, command_name, command_description, level)) @@ -382,74 +300,11 @@ class Irc: return None - def generate_help_menu_bakcup(self, nickname: str) -> None: - - # Check if the nickname is an admin - admin_obj = self.Admin.get_admin(nickname) - dnickname = self.Config.SERVICE_NICKNAME - color_bold = self.Config.COLORS.bold - color_nogc = self.Config.COLORS.nogc - color_blue = self.Config.COLORS.blue - color_black = self.Config.COLORS.black - color_underline = self.Config.COLORS.underline - current_level = 0 - count = 0 - if admin_obj is not None: - current_level = admin_obj.level - - self.Protocol.send_notice(nick_from=dnickname,nick_to=nickname, msg=f" ***************** LISTE DES COMMANDES *****************") - - for level, modules in self.module_commands.items(): - if level > current_level: - break - - if count > 0: - self.Protocol.send_notice(nick_from=dnickname, nick_to=nickname, msg=" ") - - self.Protocol.send_notice(nick_from=dnickname, nick_to=nickname, msg=f"{color_blue}{color_bold}Level {level}:{color_nogc}") - for module_name, commands in modules.items(): - self.Protocol.send_notice(nick_from=dnickname, nick_to=nickname, msg=f"{color_black} {color_underline}Module: {module_name}{color_nogc}") - for command, description in commands.items(): - self.Protocol.send_notice(nick_from=dnickname, nick_to=nickname, msg=f" {command:<20}: {description}") - - count += 1 - - self.Protocol.send_notice(nick_from=dnickname,nick_to=nickname,msg=f" ***************** FIN DES COMMANDES *****************") - return None - - def is_cmd_allowed(self, nickname: str, command_name: str) -> bool: - - admin_obj = self.Admin.get_admin(nickname) - current_level = 0 - - if admin_obj is not None: - current_level = admin_obj.level - - for level, modules in self.module_commands.items(): - for module_name, commands in modules.items(): - for command, description in commands.items(): - if command.lower() == command_name.lower() and level <= current_level: - return True - - return False - - def __create_table(self): + def __create_table(self) -> None: """## Create core tables """ pass - def load_existing_modules(self) -> None: - """Charge les modules qui existe déja dans la base de données - - Returns: - None: Aucun retour requis, elle charge puis c'est tout - """ - result = self.Base.db_execute_query(f"SELECT module_name FROM {self.Config.TABLE_MODULE}") - for r in result.fetchall(): - self.load_module('sys', r[0], True) - - return None - def get_defender_uptime(self) -> str: """Savoir depuis quand Defender est connecté @@ -471,276 +326,17 @@ class Irc: """ while self.hb_active: time.sleep(beat) - service_id = self.Config.SERVICE_ID - hsid = self.HSID self.Base.execute_periodic_action() - def create_ping_timer(self, time_to_wait:float, class_name:str, method_name: str, method_args: list=[]) -> None: - # 1. Timer créer - # 1.1 Créer la fonction a executer - # 1.2 Envoyer le ping une fois le timer terminer - # 2. Executer la fonction - try: - if not class_name in self.loaded_classes: - self.Logs.error(f"La class [{class_name} n'existe pas !!]") - return False - - class_instance = self.loaded_classes[class_name] - - t = threading.Timer(interval=time_to_wait, function=self.__create_tasks, args=(class_instance, method_name, method_args)) - t.start() - - self.Base.running_timers.append(t) - - self.Logs.debug(f"Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") - - except AssertionError as ae: - self.Logs.error(f'Assertion Error -> {ae}') - except TypeError as te: - self.Logs.error(f"Type error -> {te}") - - def __create_tasks(self, obj: object, method_name: str, param:list) -> None: - """#### Ajouter les méthodes a éxecuter dans un dictionnaire - - Args: - obj (object): Une instance de la classe qui va etre executer - method_name (str): Le nom de la méthode a executer - param (list): les parametres a faire passer - - Returns: - None: aucun retour attendu - """ - self.Base.periodic_func[len(self.Base.periodic_func) + 1] = { - 'object': obj, - 'method_name': method_name, - 'param': param - } - - self.Logs.debug(f'Function to execute : {str(self.Base.periodic_func)}') - self.send_ping_to_sereur() - return None - - def send_ping_to_sereur(self) -> None: - """### Envoyer un PING au serveur - """ - service_id = self.Config.SERVICE_ID - hsid = self.HSID - self.Protocol.send2socket(f':{service_id} PING :{hsid}') - - return None - - def load_module(self, fromuser:str, module_name:str, init:bool = False) -> bool: - try: - # module_name : mod_voice - module_name = module_name.lower() - module_folder = module_name.split('_')[1].lower() # ==> voice - class_name = module_name.split('_')[1].capitalize() # ==> Voice - - # Check if the module is already loaded. - if 'mods.' + module_folder + '.' + module_name in sys.modules: - self.Logs.debug(f"Module [{module_folder}.{module_name}] already loaded!") - if class_name in self.loaded_classes: - # Si le module existe dans la variable globale retourne False - self.Logs.debug(f"Module [{module_folder}.{module_name}] exist in the local variable!") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Le module {module_name} est déja chargé ! si vous souhaiter le recharge tapez {self.Config.SERVICE_PREFIX}reload {module_name}", - channel=self.Config.SERVICE_CHANLOG - ) - return False - - the_module = sys.modules[f'mods.{module_folder}.{module_name}'] - importlib.reload(the_module) - my_class = getattr(the_module, class_name, None) - new_instance = my_class(self.ircObject) - self.loaded_classes[class_name] = new_instance - - # Créer le module dans la base de données - if not init: - self.Base.db_record_module(fromuser, module_name) - - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Module {module_name} chargé", - channel=self.Config.SERVICE_CHANLOG - ) - self.Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!") - return True - - # Charger le module - loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') - my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe - create_instance_of_the_class = my_class(self.ircObject) # Créer une nouvelle instance de la classe - - if not hasattr(create_instance_of_the_class, 'cmd'): - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Module {module_name} ne contient pas de méthode cmd", - channel=self.Config.SERVICE_CHANLOG - ) - self.Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available") - self.Base.db_delete_module(module_name) - return False - - # Charger la nouvelle class dans la variable globale - self.loaded_classes[class_name] = create_instance_of_the_class - - # Enregistrer le module dans la base de données - if not init: - self.Base.db_record_module(fromuser, module_name) - - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Module {module_name} chargé", - channel=self.Config.SERVICE_CHANLOG - ) - - self.Logs.debug(f"Module {class_name} has been loaded") - - return True - - except ModuleNotFoundError as moduleNotFound: - self.Logs.error(f"MODULE_NOT_FOUND: {moduleNotFound}") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ {self.Config.COLORS.red}MODULE ERROR{self.Config.COLORS.black} ]: {moduleNotFound}", - channel=self.Config.SERVICE_CHANLOG - ) - self.Base.db_delete_module(module_name) - return False - - except Exception as err: - self.Logs.error(f"[LOAD MODULE ERROR]: {err}", exc_info=True) - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ {self.Config.COLORS.red}MODULE ERROR{self.Config.COLORS.black} ]: {err}", - channel=self.Config.SERVICE_CHANLOG - ) - self.Base.db_delete_module(module_name) - return False - - def unload_module(self, mod_name: str) -> bool: - """Unload a module - - Args: - mod_name (str): Module name ex mod_defender - - Returns: - bool: True if success - """ - try: - # Le nom du module. exemple: mod_defender - module_name = mod_name.lower() - module_folder = module_name.split('_')[1].lower() # ==> defender - class_name = module_name.split('_')[1].capitalize() # Nom de la class. exemple: Defender - - if class_name in self.loaded_classes: - self.loaded_classes[class_name].unload() - del self.loaded_classes[class_name] - - # Delete from the sys. - if sys.modules.get(f'{module_folder}.{module_name}'): - del sys.modules[f"{module_folder}.{module_name}"] - - # Supprimer le module de la base de données - self.Base.db_delete_module(module_name) - - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ MODULE INFO ] Module {module_name} has been deleted!", - channel=self.Config.SERVICE_CHANLOG - ) - self.Logs.debug(f"[ MODULE ] {module_name} has been deleted!") - return True - - except Exception as err: - self.Logs.error(f"General Error: {err}") - return False - - def reload_module(self, from_user: str, mod_name: str) -> bool: - try: - module_name = mod_name.lower() # ==> mod_defender - module_folder = module_name.split('_')[1].lower() # ==> defender - class_name = module_name.split('_')[1].capitalize() # ==> Defender - - if f'mods.{module_folder}.{module_name}' in sys.modules: - self.Logs.info('Unload the module ...') - self.loaded_classes[class_name].unload() - self.Logs.info('Module Already Loaded ... reloading the module ...') - - # Load dependencies - self.Base.reload_modules_with_dependencies(f'mods.{module_folder}') - the_module = sys.modules[f'mods.{module_folder}.{module_name}'] - importlib.reload(the_module) - - # Supprimer la class déja instancier - if class_name in self.loaded_classes: - del self.loaded_classes[class_name] - - my_class = getattr(the_module, class_name, None) - new_instance = my_class(self.ircObject) - self.loaded_classes[class_name] = new_instance - - self.Base.db_update_module(from_user, mod_name) - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Module {module_name} rechargé", - channel=self.Config.SERVICE_CHANLOG - ) - return False - else: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Module {module_name} n'est pas chargé !", - channel=self.Config.SERVICE_CHANLOG - ) - - except TypeError as te: - self.Logs.error(f"A TypeError raised: {te}") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"A TypeError raised: {te}", - channel=self.Config.SERVICE_CHANLOG - ) - self.Base.db_delete_module(module_name) - except AttributeError as ae: - self.Logs.error(f"Missing Attribute: {ae}") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Missing Attribute: {ae}", - channel=self.Config.SERVICE_CHANLOG - ) - self.Base.db_delete_module(module_name) - except KeyError as ke: - self.Logs.error(f"Key Error: {ke}") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Key Error: {ke}", - channel=self.Config.SERVICE_CHANLOG - ) - self.Base.db_delete_module(module_name) - except Exception as e: - self.Logs.error(f"Something went wrong with a module you want to reload: {e}") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Something went wrong with the module: {e}", - channel=self.Config.SERVICE_CHANLOG - ) - self.Base.db_delete_module(module_name) - def insert_db_admin(self, uid:str, level:int) -> None: - - if self.User.get_User(uid) is None: + user_obj = self.User.get_user(uid) + if user_obj is None: return None - - getUser = self.User.get_user_asdict(uid) - - level = int(level) - + self.Admin.insert( self.Loader.Definition.MAdmin( - **getUser, - level=level + **user_obj.to_dict(), + level=int(level) ) ) @@ -760,7 +356,7 @@ class Irc: # > addaccess [nickname] [level] [password] - get_user = self.User.get_User(nickname) + get_user = self.User.get_user(nickname) level = self.Base.convert_to_int(level) password = password @@ -808,20 +404,6 @@ class Irc: self.Logs.info(response) return response - def logs(self, log_msg:str) -> None: - """Log to database if you want - - Args: - log_msg (str): the message to log - """ - try: - mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'server_msg': log_msg} - self.Base.db_execute_query(f'INSERT INTO {self.Config.TABLE_LOG} (datetime, server_msg) VALUES (:datetime, :server_msg)', mes_donnees) - - return None - except Exception as err: - self.Logs.error(f"General Error: {err}") - def thread_check_for_new_version(self, fromuser: str) -> None: dnickname = self.Config.SERVICE_NICKNAME @@ -841,31 +423,20 @@ class Irc: """ try: original_response: list[str] = data.copy() - interm_response: list[str] = data.copy() - """This the original without first value""" - interm_response.pop(0) - if len(original_response) == 0 or len(original_response) == 1: + if len(original_response) < 2: self.Logs.warning(f'Size ({str(len(original_response))}) - {original_response}') - return False - - if len(original_response) == 7: - if original_response[2] == 'PRIVMSG' and original_response[4] == ':auth': - data_copy = original_response.copy() - data_copy[6] = '**********' - self.Logs.debug(f">> {data_copy}") - else: - self.Logs.debug(f">> {original_response}") - else: - self.Logs.debug(f">> {original_response}") + return None + self.Logs.debug(self.Utils.hide_sensitive_data(original_response)) parsed_protocol = self.Protocol.parse_server_msg(original_response.copy()) - match parsed_protocol: case 'PING': self.Protocol.on_server_ping(serverMsg=original_response) - return None + + case 'SERVER': + self.Protocol.on_server(serverMsg=original_response) case 'SJOIN': self.Protocol.on_sjoin(serverMsg=original_response) @@ -876,10 +447,9 @@ class Irc: case 'UID': try: self.Protocol.on_uid(serverMsg=original_response) - - for classe_name, classe_object in self.loaded_classes.items(): - classe_object.cmd(original_response) - + for module in self.ModuleUtils.model_get_loaded_modules().copy(): + module.class_instance.cmd(original_response) + return None except Exception as err: self.Logs.error(f'General Error: {err}') @@ -913,38 +483,38 @@ class Irc: self.Protocol.on_reputation(serverMsg=original_response) case 'SLOG': # TODO - self.Logs.debug(f"** handle {parsed_protocol}") + self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") case 'MD': # TODO - self.Logs.debug(f"** handle {parsed_protocol}") + self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") case 'PRIVMSG': self.Protocol.on_privmsg(serverMsg=original_response) case 'PONG': # TODO - self.Logs.debug(f"** handle {parsed_protocol}") + self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") case 'MODE': # TODO #['@msgid=d0ySx56Yd0nc35oHts2SkC-/J9mVUA1hfM6...', ':001', 'MODE', '#a', '+nt', '1723207536'] #['@unrealircd.org/userhost=adator@localhost;...', ':001LQ0L0C', 'MODE', '#services', '-l'] - self.Logs.debug(f"** handle {parsed_protocol}") + self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") case '320': # TODO #:irc.deb.biz.st 320 PyDefender IRCParis07 :is in security-groups: known-users,webirc-users,tls-and-known-users,tls-users - self.Logs.debug(f"** handle {parsed_protocol}") + self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") case '318': # TODO #:irc.deb.biz.st 318 PyDefender IRCParis93 :End of /WHOIS list. - self.Logs.debug(f"** handle {parsed_protocol}") + self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") case None: - self.Logs.debug(f"** TO BE HANDLE {original_response}") + self.Logs.debug(f"[!] TO HANDLE: {original_response}") if len(original_response) > 2: if original_response[2] != 'UID': # Envoyer la commande aux classes dynamiquement chargées - for classe_name, classe_object in self.loaded_classes.items(): - classe_object.cmd(original_response) + for module in self.ModuleUtils.model_get_loaded_modules().copy(): + module.class_instance.cmd(original_response) except IndexError as ie: self.Logs.error(f"IndexError: {ie}") @@ -976,14 +546,13 @@ class Irc: else: return False - is_command_allowed = self.is_cmd_allowed(fromuser, command) - if not is_command_allowed: + if not self.Commands.is_client_allowed_to_run_command(fromuser, command): command = 'notallowed' # Envoyer la commande aux classes dynamiquement chargées if command != 'notallowed': - for classe_name, classe_object in self.loaded_classes.items(): - classe_object.hcmds(user, channel, cmd, fullcmd) + for module in self.ModuleUtils.DB_MODULES: + module.class_instance.hcmds(user, channel, cmd, fullcmd) match command: @@ -1116,13 +685,13 @@ class Irc: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Your current nickname is different from the nickname you want to log in with") return False - if not user_to_log is None: + if user_to_log: mes_donnees = {'user': user_to_log, 'password': self.Loader.Utils.hash_password(password)} query = f"SELECT id, level FROM {self.Config.TABLE_ADMIN} WHERE user = :user AND password = :password" result = self.Base.db_execute_query(query, mes_donnees) user_from_db = result.fetchone() - if not user_from_db is None: + if user_from_db: uid_user = self.User.get_uid(user_to_log) self.insert_db_admin(uid_user, user_from_db[1]) self.Protocol.send_priv_msg(nick_from=dnickname, @@ -1270,7 +839,7 @@ class Irc: self.Logs.warning(f":{dnickname} NOTICE {fromuser} : Impossible de supprimer l'utilisateur.") case 'register': - # Register PASSWORD EMAIL + # Syntax. Register PASSWORD EMAIL try: if len(cmd) < 3: @@ -1292,7 +861,7 @@ class Irc: ) return None - user_obj = self.User.get_User(fromuser) + user_obj = self.User.get_user(fromuser) if user_obj is None: self.Logs.error(f"Nickname ({fromuser}) doesn't exist, it is impossible to register this nickname") @@ -1347,7 +916,7 @@ class Irc: account = str(cmd[1]) # account encrypted_password = self.Loader.Utils.hash_password(cmd[2]) - user_obj = self.User.get_User(fromuser) + user_obj = self.User.get_user(fromuser) client_obj = self.Client.get_Client(user_obj.uid) if client_obj is not None: @@ -1356,22 +925,15 @@ class Irc: db_query = f"SELECT account FROM {self.Config.TABLE_CLIENT} WHERE account = :account AND password = :password" db_param = {'account': account, 'password': encrypted_password} - exec_query = self.Base.db_execute_query( - db_query, - db_param - ) + exec_query = self.Base.db_execute_query(db_query, db_param) result_query = exec_query.fetchone() if result_query: account = result_query[0] self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You are now logged in") - client = self.Loader.Definition.MClient( - uid=user_obj.uid, account=account, nickname=fromuser, - username=user_obj.username, realname=user_obj.realname, hostname=user_obj.hostname, umodes=user_obj.umodes, vhost=user_obj.vhost, - isWebirc=user_obj.isWebirc, isWebsocket=user_obj.isWebsocket, remote_ip=user_obj.remote_ip, score_connexion=user_obj.score_connexion, - geoip=user_obj.geoip, connexion_datetime=user_obj.connexion_datetime - ) + client = self.Loader.Definition.MClient(**user_obj.to_dict(), account=account) self.Client.insert(client) - self.Protocol.send_svs_mode(nickname=fromuser, user_mode='+r') + self.Protocol.send_svslogin(user_obj.uid, account) + self.Protocol.send_svs2mode(nickname=fromuser, user_mode='+r') else: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Wrong password or account") @@ -1395,7 +957,7 @@ class Irc: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} ") return None - user_obj = self.User.get_User(fromuser) + user_obj = self.User.get_user(fromuser) if user_obj is None: self.Logs.error(f"The User [{fromuser}] is not available in the database") return None @@ -1406,7 +968,8 @@ class Irc: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Nothing to logout. please login first") return None - self.Protocol.send_svs_mode(nickname=fromuser, user_mode='-r') + self.Protocol.send_svslogout(client_obj) + # self.Protocol.send_svsmode(nickname=fromuser, user_mode='-r') self.Client.delete(user_obj.uid) self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You have been logged out successfully") @@ -1421,12 +984,14 @@ class Irc: # Syntax. !help [module_name] module_name = str(cmd[1]) if len(cmd) == 2 else None self.generate_help_menu(nickname=fromuser, module=module_name) + return None case 'load': try: # Load a module ex: .load mod_defender mod_name = str(cmd[1]) - self.load_module(fromuser, mod_name) + self.ModuleUtils.load_one_module(self, mod_name, fromuser) + return None except KeyError as ke: self.Logs.error(f"Key Error: {ke} - list recieved: {cmd}") except Exception as err: @@ -1435,16 +1000,20 @@ class Irc: case 'unload': # unload mod_defender try: - module_name = str(cmd[1]).lower() # Le nom du module. exemple: mod_defender - self.unload_module(module_name) + # The module name. exemple: mod_defender + module_name = str(cmd[1]).lower() + self.ModuleUtils.unload_one_module(self, module_name, False) + return None except Exception as err: self.Logs.error(f"General Error: {err}") case 'reload': # reload mod_defender try: - module_name = str(cmd[1]).lower() # ==> mod_defender - self.reload_module(from_user=fromuser, mod_name=module_name) + # ==> mod_defender + module_name = str(cmd[1]).lower() + self.ModuleUtils.reload_one_module(self, module_name, fromuser) + return None except Exception as e: self.Logs.error(f"Something went wrong with a module you want to reload: {e}") self.Protocol.send_priv_msg( @@ -1452,19 +1021,20 @@ class Irc: msg=f"Something went wrong with the module: {e}", channel=dchanlog ) - self.Base.db_delete_module(module_name) + self.ModuleUtils.db_delete_module(module_name) case 'quit': try: - final_reason = ' '.join(cmd[1:]) - self.hb_active = False self.Base.shutdown() self.Base.execute_periodic_action() for chan_name in self.Channel.UID_CHANNEL_DB: self.Protocol.send_mode_chan(chan_name.name, '-l') + + for client in self.Client.CLIENT_DB: + self.Protocol.send_svslogout(client) self.Protocol.send_notice( nick_from=dnickname, @@ -1480,109 +1050,19 @@ class Irc: self.Logs.error(f'{ie}') case 'restart': - reason = [] - for i in range(1, len(cmd)): - reason.append(cmd[i]) - final_reason = ' '.join(reason) - - self.Protocol.send_notice( - nick_from=dnickname, - nick_to=fromuser, - msg=f"Redémarrage du service {dnickname}" - ) - - for class_name in self.loaded_classes: - self.loaded_classes[class_name].unload() - - self.User.UID_DB.clear() # Clear User Object - self.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object - self.Base.garbage_collector_thread() - - self.Protocol.send_squit(server_id=self.Config.SERVEUR_ID, server_link=self.Config.SERVEUR_LINK, reason=final_reason) - self.Logs.info(f'Redémarrage du server {dnickname}') - self.loaded_classes.clear() + final_reason = ' '.join(cmd[1:]) + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{dnickname.capitalize()} is going to restart!") self.Config.DEFENDER_RESTART = 1 # Set restart status to 1 saying that the service will restart self.Config.DEFENDER_INIT = 1 # set init to 1 saying that the service will be re initiated - self.Loader.ServiceLogging.remove_logger() case 'rehash': - self.Loader.ServiceLogging.remove_logger() - self.Loader.ServiceLogging = self.Loader.LoggingModule.ServiceLogging() - self.Loader.Logs = self.Loader.ServiceLogging.get_logger() - - need_a_restart = ["SERVEUR_ID"] - restart_flag = False - Config_bakcup = self.Config.to_dict().copy() - serveur_id = self.Config.SERVEUR_ID - service_nickname = self.Config.SERVICE_NICKNAME - hsid = self.Config.HSID - ssl_version = self.Config.SSL_VERSION - defender_init = self.Config.DEFENDER_INIT - defender_restart = self.Config.DEFENDER_RESTART - current_version = self.Config.CURRENT_VERSION - latest_version = self.Config.LATEST_VERSION - - mods = ["core.definition", "core.config", "core.base", "core.classes.protocols.unreal6", "core.classes.protocol"] - - mod_unreal6 = sys.modules['core.classes.protocols.unreal6'] - mod_protocol = sys.modules['core.classes.protocol'] - mod_base = sys.modules['core.base'] - mod_config = sys.modules['core.classes.config'] - mod_definition = sys.modules['core.definition'] - - importlib.reload(mod_definition) - importlib.reload(mod_config) - self.Config = self.Loader.ConfModule.Configuration(self.Loader.Logs).ConfigObject - self.Config.HSID = hsid - self.Config.DEFENDER_INIT = defender_init - self.Config.DEFENDER_RESTART = defender_restart - self.Config.SSL_VERSION = ssl_version - self.Config.CURRENT_VERSION = current_version - self.Config.LATEST_VERSION = latest_version - importlib.reload(mod_base) - - conf_bkp_dict: dict = Config_bakcup - config_dict: dict = self.Config.to_dict() - - for key, value in conf_bkp_dict.items(): - if config_dict[key] != value and key != 'COLORS': - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f'[{key}]: {value} ==> {config_dict[key]}', - channel=self.Config.SERVICE_CHANLOG - ) - if key in need_a_restart: - restart_flag = True - - if service_nickname != self.Config.SERVICE_NICKNAME: - self.Protocol.send_set_nick(self.Config.SERVICE_NICKNAME) - - if restart_flag: - self.Config.SERVEUR_ID = serveur_id - self.Protocol.send_priv_msg(nick_from=self.Config.SERVICE_NICKNAME, msg='You need to restart defender !', channel=self.Config.SERVICE_CHANLOG) - - self.Base = self.Loader.BaseModule.Base(self.Loader) - - importlib.reload(mod_unreal6) - importlib.reload(mod_protocol) - - self.Protocol = Protocol(self.Config.SERVEUR_PROTOCOL, self.ircObject).Protocol - - for mod in mods: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f'> Module [{mod}] reloaded', - channel=self.Config.SERVICE_CHANLOG - ) - for mod in self.Base.get_all_modules(): - self.reload_module(fromuser, mod) + rehash.rehash_service(self.ircObject, fromuser) + return None case 'show_modules': - - self.Logs.debug(self.loaded_classes) - all_modules = self.Base.get_all_modules() + self.Logs.debug('List of modules: ' + ', '.join([module.module_name for module in self.ModuleUtils.model_get_loaded_modules()])) + all_modules = self.ModuleUtils.get_all_available_modules() loaded = False - results = self.Base.db_execute_query(f'SELECT datetime, user, module_name FROM {self.Config.TABLE_MODULE}') results = results.fetchall() @@ -1608,32 +1088,31 @@ class Irc: ) case 'show_timers': - if self.Base.running_timers: for the_timer in self.Base.running_timers: self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, - msg=f">> {the_timer.getName()} - {the_timer.is_alive()}" + msg=f">> {the_timer.name} - {the_timer.is_alive()}" ) else: self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, - msg="Aucun timers en cours d'execution" + msg="There is no timers that are running!" ) + return None case 'show_threads': - for thread in self.Base.running_threads: self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, - msg=f">> {thread.getName()} ({thread.is_alive()})" + msg=f">> {thread.name} ({thread.is_alive()})" ) + return None case 'show_channels': - for chan in self.Channel.UID_CHANNEL_DB: list_nicknames: list = [] for uid in chan.uids: @@ -1646,6 +1125,7 @@ class Irc: nick_to=fromuser, msg=f"Channel: {chan.name} - Users: {list_nicknames}" ) + return None case 'show_users': count_users = len(self.User.UID_DB) @@ -1656,6 +1136,7 @@ class Irc: nick_to=fromuser, msg=f"UID : {db_user.uid} - isWebirc: {db_user.isWebirc} - isWebSocket: {db_user.isWebsocket} - Nickname: {db_user.nickname} - Connection: {db_user.connexion_datetime}" ) + return None case 'show_clients': count_users = len(self.Client.CLIENT_DB) @@ -1666,26 +1147,25 @@ class Irc: nick_to=fromuser, msg=f"UID : {db_client.uid} - isWebirc: {db_client.isWebirc} - isWebSocket: {db_client.isWebsocket} - Nickname: {db_client.nickname} - Account: {db_client.account} - Connection: {db_client.connexion_datetime}" ) + return None case 'show_admins': - for db_admin in self.Admin.UID_ADMIN_DB: self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, msg=f"UID : {db_admin.uid} - Nickname: {db_admin.nickname} - Level: {db_admin.level} - Connection: {db_admin.connexion_datetime}" ) + return None case 'show_configuration': - - config_dict = self.Config.__dict__ - - for key, value in config_dict.items(): + for key, value in self.Config.to_dict().items(): self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, msg=f'{key} = {value}' ) + return None case 'uptime': uptime = self.get_defender_uptime() @@ -1694,24 +1174,24 @@ class Irc: nick_to=fromuser, msg=f"{uptime}" ) + return None case 'copyright': self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, - msg=f"# Defender V.{self.Config.CURRENT_VERSION} Developped by adator® #" + msg=f">> Defender V.{self.Config.CURRENT_VERSION} Developped by adator®." ) + return None case 'checkversion': - - self.Base.create_thread( - self.thread_check_for_new_version, - (fromuser, ) - ) + self.Base.create_thread(self.thread_check_for_new_version, (fromuser, )) + return None case 'raw': raw_command = ' '.join(cmd[1:]) self.Protocol.send_raw(raw_command) + return None case _: pass diff --git a/core/loader.py b/core/loader.py index 6a75451..6349e5a 100644 --- a/core/loader.py +++ b/core/loader.py @@ -1,10 +1,12 @@ from logging import Logger -from core.classes import user, admin, client, channel, reputation, settings, commands +from core.classes import user, admin, client, channel, reputation, settings import core.logs as logs import core.definition as df import core.utils as utils -import core.base as base_module -import core.classes.config as conf_module +import core.base as base_mod +import core.module as module_mod +import core.classes.commands as commands_mod +import core.classes.config as conf_mod class Loader: @@ -13,24 +15,26 @@ class Loader: # Load Main Modules self.Definition: df = df - self.ConfModule: conf_module = conf_module + self.ConfModule: conf_mod = conf_mod - self.BaseModule: base_module = base_module + self.BaseModule: base_mod = base_mod - self.Utils: utils = utils + self.CommandModule: commands_mod = commands_mod self.LoggingModule: logs = logs + self.Utils: utils = utils + # Load Classes - self.ServiceLogging: logs.ServiceLogging = logs.ServiceLogging() + self.ServiceLogging: logs.ServiceLogging = self.LoggingModule.ServiceLogging() self.Logs: Logger = self.ServiceLogging.get_logger() self.Settings: settings.Settings = settings.Settings() - self.Config: df.MConfig = self.ConfModule.Configuration(self.Logs).ConfigObject + self.Config: df.MConfig = self.ConfModule.Configuration(self).get_config_model() - self.Base: base_module.Base = self.BaseModule.Base(self) + self.Base: base_mod.Base = self.BaseModule.Base(self) self.User: user.User = user.User(self) @@ -42,4 +46,8 @@ class Loader: self.Reputation: reputation.Reputation = reputation.Reputation(self) - self.Commands: commands.Command = commands.Command(self) + self.Commands: commands_mod.Command = commands_mod.Command(self) + + self.ModuleUtils: module_mod.Module = module_mod.Module(self) + + self.Logs.debug("LOADER Success!") diff --git a/core/logs.py b/core/logs.py index bef6ab2..19f2e62 100644 --- a/core/logs.py +++ b/core/logs.py @@ -9,6 +9,8 @@ class ServiceLogging: """ self.OS_SEP = sep self.LOGGING_NAME = loggin_name + self.remove_logger(loggin_name) # Remove logger if exists + self.DEBUG_LEVEL, self.DEBUG_FILE_LEVEL, self.DEBUG_STDOUT_LEVEL = (10, 10, 10) self.SERVER_PREFIX = None self.LOGGING_CONSOLE = True @@ -85,21 +87,77 @@ class ServiceLogging: # Apply the filter logs.addFilter(self.replace_filter) - logs.info(f'#################### STARTING {self.LOGGING_NAME} ####################') return logs def set_stdout_handler_level(self, level: int) -> None: + self.logs.debug(f"[STDOUT LEVEL] New level {level}") self.stdout_handler.setLevel(level) def set_file_handler_level(self, level: int) -> None: + self.logs.debug(f"[LOG FILE LEVEL] new level {level}") self.file_handler.setLevel(level) + def update_handler_format(self, debug_hard: bool = False) -> None: + """Updating logging formatter format! + + Args: + debug_hard (bool, optional): If true you will have filename, + function name and the line number. Defaults to False. + """ + # Updating logging formatter + if debug_hard: + new_formatter = logging.Formatter( + fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)', + datefmt='%Y-%m-%d %H:%M:%S' + ) + else: + new_formatter = logging.Formatter( + fmt='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + for handler in self.logs.handlers: + handler.setFormatter(new_formatter) + + def regenerate_handlers(self, logger: logging.Logger) -> logging.Logger: + os_sep = self.OS_SEP + logging_name = self.LOGGING_NAME + debug_file_level = self.DEBUG_FILE_LEVEL + debug_stdout_level = self.DEBUG_STDOUT_LEVEL + + # Add Handlers + self.file_handler = logging.FileHandler(f'logs{os_sep}{logging_name}.log',encoding='UTF-8') + self.file_handler.setLevel(debug_file_level) + + self.stdout_handler = logging.StreamHandler() + self.stdout_handler.setLevel(debug_stdout_level) + + # Define log format + formatter = logging.Formatter( + fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + # Apply log format + self.file_handler.setFormatter(formatter) + self.stdout_handler.setFormatter(formatter) + + # Add handler to logs + logger.addHandler(self.file_handler) + logger.addHandler(self.stdout_handler) + + # Apply the filter + logger.addFilter(self.replace_filter) + logger.info(f'REGENRATING LOGGER {self.LOGGING_NAME}') + + return logger + def replace_filter(self, record: logging.LogRecord) -> bool: response = True - filter: list[str] = ['PING', f":{self.SERVER_PREFIX}auth", "['PASS'"] + filter: list[str] = self.LOG_FILTERS # record.msg = record.getMessage().replace("PING", "[REDACTED]") # if self.LOGGING_CONSOLE: @@ -109,4 +167,4 @@ class ServiceLogging: if f in record.getMessage(): response = False - return response # Retourne True pour permettre l'affichage du message + return response # Retourne True to write the log! diff --git a/core/module.py b/core/module.py new file mode 100644 index 0000000..d3c9421 --- /dev/null +++ b/core/module.py @@ -0,0 +1,424 @@ +''' +This is the main operational file to handle modules +''' +from pathlib import Path +import sys +import importlib +from types import ModuleType +from typing import TYPE_CHECKING, Optional +from core.definition import MModule + +if TYPE_CHECKING: + from core.loader import Loader + from core.irc import Irc + +class Module: + + DB_MODULES: list[MModule] = [] + + def __init__(self, loader: 'Loader') -> None: + self.__Loader = loader + self.__Base = loader.Base + self.__Logs = loader.Logs + self.__Utils = loader.Utils + self.__Config = loader.Config + + def get_all_available_modules(self) -> list[str]: + """Get list of all main modules + using this pattern mod_*.py + all files starting with mod_ + Returns: + list[str]: List of all module names. + """ + base_path = Path('mods') + return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')] + + def get_module_information(self, module_name: str) -> tuple[str, str, str]: + # module_name : mod_defender + if not module_name.lower().startswith('mod_'): + return None, None, None + + module_name = module_name.lower() + module_folder = module_name.split('_')[1].lower() # --> defender + class_name = module_name.split('_')[1].capitalize() # --> Defender + return module_folder, module_name, class_name + + def load_one_module(self, uplink: 'Irc', module_name: str, nickname: str, is_default: bool = False) -> bool: + + module_folder, module_name, class_name = self.get_module_information(module_name) + + if module_folder is None or module_name is None or class_name is None: + self.__Logs.error(f"There is an error with the module name! {module_folder}, {module_name}, {class_name}") + return False + + if self.is_module_exist_in_sys_module(module_name): + self.__Logs.debug(f"Module [{module_folder}.{module_name}] already loaded!") + if self.model_is_module_exist(module_name): + # Si le module existe dans la variable globale retourne False + self.__Logs.debug(f"Module [{module_folder}.{module_name}] exist in the local variable!") + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"Le module {module_name} est déja chargé ! si vous souhaiter le recharge tapez {self.__Config.SERVICE_PREFIX}reload {module_name}", + channel=self.__Config.SERVICE_CHANLOG + ) + return False + + return self.reload_one_module(uplink, module_name, nickname) + + # Charger le module + loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') + my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe + create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe + + if not hasattr(create_instance_of_the_class, 'cmd'): + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"Module {module_name} ne contient pas de méthode cmd", + channel=self.__Config.SERVICE_CHANLOG + ) + self.__Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available") + self.db_delete_module(module_name) + return False + + # Charger la nouvelle class dans la variable globale + if self.model_insert_module(MModule(module_name, class_name, create_instance_of_the_class)): + # Enregistrer le module dans la base de données + self.db_register_module(module_name, nickname, is_default) + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"Module {module_name} chargé", + channel=self.__Config.SERVICE_CHANLOG + ) + + self.__Logs.debug(f"Module {class_name} has been loaded") + + def load_all_modules(self) -> bool: + ... + + def reload_one_module(self, uplink: 'Irc', module_name: str, nickname: str) -> bool: + """Reloading one module and insert it into the model as well as the database + + Args: + uplink (Irc): The Irc service instance + module_name (str): The module name + nickname (str): The nickname + + Returns: + bool: True if the module has been reloaded + """ + module_folder, module_name, class_name = self.get_module_information(module_name) + red = self.__Config.COLORS.red + nogc = self.__Config.COLORS.nogc + try: + if self.is_module_exist_in_sys_module(module_name): + module_model = self.model_get_module(module_name) + if module_model: + module_model.class_instance.unload() + else: + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] hasn't been reloaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}", + channel=self.__Config.SERVICE_CHANLOG + ) + self.__Logs.debug(f"Module [{module_folder}.{module_name}] not found! Please use {self.__Config.SERVICE_PREFIX}load {module_name}") + return False + + # reload module dependencies + self.reload_all_modules_with_all_dependencies(f'mods.{module_folder}') + + the_module = sys.modules[f'mods.{module_folder}.{module_name}'] + importlib.reload(the_module) + my_class = getattr(the_module, class_name, None) + new_instance = my_class(uplink) + module_model.class_instance = new_instance + + # Créer le module dans la base de données + self.db_register_module(module_name, nickname) + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"Module [{module_folder}.{module_name}] has been reloaded!", + channel=self.__Config.SERVICE_CHANLOG + ) + self.__Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!") + return True + else: + # Module is not loaded! Nothing to reload + self.__Logs.debug(f"[RELOAD MODULE ERROR] [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}") + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}", + channel=self.__Config.SERVICE_CHANLOG + ) + return False + + except (TypeError, AttributeError, KeyError, Exception) as err: + self.__Logs.error(f"[RELOAD MODULE ERROR]: {err}") + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"[RELOAD MODULE ERROR]: {err}", + channel=self.__Config.SERVICE_CHANLOG + ) + self.db_delete_module(module_name) + + def reload_all_modules(self) -> bool: + ... + + def reload_all_modules_with_all_dependencies(self, prefix: str = 'mods') -> bool: + """ + Reload all modules in sys.modules that start with the given prefix. + Useful for reloading a full package during development. + """ + modules_to_reload = [] + + # Collect target modules + for name, module in sys.modules.items(): + if ( + isinstance(module, ModuleType) + and module is not None + and name.startswith(prefix) + ): + modules_to_reload.append((name, module)) + + # Sort to reload submodules before parent modules + for name, module in sorted(modules_to_reload, key=lambda x: x[0], reverse=True): + try: + if 'mod_' not in name and 'schemas' not in name: + importlib.reload(module) + self.__Logs.debug(f'[LOAD_MODULE] Module {module} success') + + except Exception as err: + self.__Logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}') + + def unload_one_module(self, uplink: 'Irc', module_name: str, keep_in_db: bool = True) -> bool: + """Unload a module + + Args: + mod_name (str): Module name ex mod_defender + + Returns: + bool: True if success + """ + try: + # Le nom du module. exemple: mod_defender + red = self.__Config.COLORS.red + nogc = self.__Config.COLORS.nogc + module_folder, module_name, class_name = self.get_module_information(module_name) + module = self.model_get_module(module_name) + if module is None: + self.__Logs.debug(f"[ UNLOAD MODULE ERROR ] This module {module_name} is not loaded!") + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"[ {red}UNLOAD MODULE ERROR{nogc} ] This module {module_name} is not loaded!", + channel=self.__Config.SERVICE_CHANLOG + ) + return False + + if module: + module.class_instance.unload() + self.DB_MODULES.remove(module) + + # Delete from the sys.modules. + if sys.modules.get(f'mods.{module_folder}.{module_name}'): + del sys.modules[f"mods.{module_folder}.{module_name}"] + + if sys.modules.get(f'mods.{module_folder}.{module_name}'): + self.__Logs.debug(f"Module mods.{module_folder}.{module_name} still in the sys.modules") + + # Supprimer le module de la base de données + if not keep_in_db: + self.db_delete_module(module_name) + + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"[ UNLOAD MODULE INFO ] Module {module_name} has been unloaded!", + channel=self.__Config.SERVICE_CHANLOG + ) + self.__Logs.debug(f"[ UNLOAD MODULE ] {module_name} has been unloaded!") + return True + + self.__Logs.debug(f"[UNLOAD MODULE]: Module {module_name} not found in DB_MODULES variable!") + return False + + except Exception as err: + self.__Logs.error(f"General Error: {err}") + return False + + def unload_all_modules(self) -> bool: + ... + + def is_module_exist_in_sys_module(self, module_name: str) -> bool: + """Check if the module exist in the sys.modules + This will check only in the folder mods/ + Args: + module_name (str): The module name + + Returns: + bool: True if the module exist + """ + module_folder, module_name, class_name = self.get_module_information(module_name) + if "mods." + module_folder + "." + module_name in sys.modules: + return True + return False + + ''' + ALL METHODS RELATED TO THE MModule MODEL DATACLASS + ''' + def model_get_module(self, module_name: str) -> Optional[MModule]: + """Get The module model object if exist otherwise it returns None + + Args: + module_name (str): The module name you want to fetch + + Returns: + Optional[MModule]: The Module Model Object + """ + for module in self.DB_MODULES: + if module.module_name.lower() == module_name.lower(): + self.__Logs.debug(f"[MODEL MODULE GET] The module {module_name} has been found in the model DB_MODULES") + return module + + self.__Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES") + return None + + def model_get_loaded_modules(self) -> list[MModule]: + """Get the instance of DB_MODULES. + Warning: You should use a copy if you want to loop through the list! + + Returns: + list[MModule]: A list of module model object + """ + self.__Logs.debug(f"[MODEL MODULE LOADED MODULES] {len(self.DB_MODULES)} modules found!") + return self.DB_MODULES + + def model_insert_module(self, module_model: MModule) -> bool: + """Insert a new module model object + + Args: + module_model (MModule): The module model object + + Returns: + bool: True if the model has been inserted + """ + module = self.model_get_module(module_model.module_name) + if module is None: + self.DB_MODULES.append(module_model) + self.__Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} has been inserted in the local variable model DB_MODULES") + return True + + self.__Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} already exist in the local variable model DB_MODULES") + return False + + def model_clear(self) -> None: + """Clear DB_MODULES list! + """ + self.DB_MODULES.clear() + self.__Logs.debug("[MODEL MODULE CLEAR] The local variable model DB_MODULES has been cleared") + return None + + def model_is_module_exist(self, module_name: str) -> bool: + """Check if the module exist in the module model object + + Args: + module_name (str): The module name + + Returns: + bool: True if the module_name exist + """ + if self.model_get_module(module_name): + self.__Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} exist in the local model DB_MODULES!") + return True + + self.__Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} is not available in the local model DB_MODULES!") + return False + + ''' + OPERATION DEDICATED TO DATABASE MANAGEMENT + ''' + + def db_load_all_existing_modules(self, uplink: 'Irc') -> bool: + """Charge les modules qui existe déja dans la base de données + + Returns: + None: Aucun retour requis, elle charge puis c'est tout + """ + self.__Logs.debug("[DB LOAD MODULE] Loading modules from the database!") + result = self.__Base.db_execute_query(f"SELECT module_name FROM {self.__Config.TABLE_MODULE}") + for r in result.fetchall(): + self.load_one_module(uplink, r[0], 'sys', True) + + return True + + def db_is_module_exist(self, module_name: str) -> bool: + """Check if the module exist in the database + + Args: + module_name (str): The module name you want to check + + Returns: + bool: True if the module exist in the database + """ + query = f"SELECT id FROM {self.__Config.TABLE_MODULE} WHERE module_name = :module_name" + mes_donnes = {'module_name': module_name.lower()} + results = self.__Base.db_execute_query(query, mes_donnes) + + if results.fetchall(): + self.__Logs.debug(f"[DB MODULE EXIST] The module {module_name} exist in the database!") + return True + else: + self.__Logs.debug(f"[DB MODULE EXIST] The module {module_name} is not available in the database!") + return False + + def db_register_module(self, module_name: str, nickname: str, is_default: bool = False) -> bool: + """Insert a new module in the database + + Args: + module_name (str): The module name + nickname (str): The user who loaded the module + isdefault (int): Is this a default module. Default 0 + """ + if not self.db_is_module_exist(module_name): + insert_cmd_query = f"INSERT INTO {self.__Config.TABLE_MODULE} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)" + mes_donnees = {'datetime': self.__Utils.get_sdatetime(), 'user': nickname, 'module_name': module_name.lower(), 'isdefault': is_default} + insert = self.__Base.db_execute_query(insert_cmd_query, mes_donnees) + if insert.rowcount > 0: + self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} has been inserted to the database!") + return True + else: + self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} not inserted to the database!") + return False + + self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} already exist in the database! Nothing to insert!") + return False + + def db_update_module(self, module_name: str, nickname: str) -> None: + """Update the datetime and the user that updated the module + + Args: + module_name (str): The module name to update + nickname (str): The nickname who updated the module + """ + update_cmd_query = f"UPDATE {self.__Config.TABLE_MODULE} SET datetime = :datetime, LOWER(user) = :user WHERE LOWER(module_name) = :module_name" + mes_donnees = {'datetime': self.__Utils.get_sdatetime(), 'user': nickname.lower(), 'module_name': module_name.lower()} + result = self.__Base.db_execute_query(update_cmd_query, mes_donnees) + if result.rowcount > 0: + self.__Logs.debug(f"[DB UPDATE MODULE] Module {module_name} has been updated!") + return True + else: + self.__Logs.debug(f"[DB UPDATE MODULE] Module {module_name} not found! Nothing to update!") + return False + + def db_delete_module(self, module_name:str) -> None: + """Delete a module from the database + + Args: + module_name (str): The module name you want to delete + """ + insert_cmd_query = f"DELETE FROM {self.__Config.TABLE_MODULE} WHERE LOWER(module_name) = :module_name" + mes_donnees = {'module_name': module_name.lower()} + delete = self.__Base.db_execute_query(insert_cmd_query, mes_donnees) + if delete.rowcount > 0: + self.__Logs.debug(f"[DB MODULE DELETE] The module {module_name} has been deleted from the dabatase!") + return True + + self.__Logs.debug(f"[DB MODULE DELETE] The module {module_name} is not available in the database! Nothing to delete!") + return False diff --git a/core/utils.py b/core/utils.py index b89551d..e9b86ac 100644 --- a/core/utils.py +++ b/core/utils.py @@ -2,14 +2,20 @@ Main utils library. ''' import gc +import ssl +import socket from pathlib import Path -from re import sub -from typing import Literal, Optional, Any +from re import match, sub +import sys +from typing import Literal, Optional, Any, TYPE_CHECKING from datetime import datetime, timedelta, timezone from time import time from random import choice from hashlib import md5, sha3_512 +if TYPE_CHECKING: + from core.irc import Irc + def convert_to_int(value: Any) -> Optional[int]: """Convert a value to int @@ -51,6 +57,48 @@ def get_datetime() -> datetime: """ return datetime.now() +def get_ssl_context() -> ssl.SSLContext: + """Generate the ssl context + + Returns: + SSLContext: The SSL Context + """ + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + return ctx + +def create_socket(uplink: 'Irc') -> None: + """Create a socket to connect SSL or Normal connection + """ + try: + soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) + connexion_information = (uplink.Config.SERVEUR_IP, uplink.Config.SERVEUR_PORT) + + if uplink.Config.SERVEUR_SSL: + # Create SSL Context object + ssl_context = get_ssl_context() + ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=uplink.Config.SERVEUR_HOSTNAME) + ssl_connexion.connect(connexion_information) + uplink.IrcSocket = ssl_connexion + uplink.Config.SSL_VERSION = uplink.IrcSocket.version() + uplink.Logs.info(f"-- Connected using SSL : Version = {uplink.Config.SSL_VERSION}") + else: + soc.connect(connexion_information) + uplink.IrcSocket = soc + uplink.Logs.info("-- Connected in a normal mode!") + + return None + + except (ssl.SSLEOFError, ssl.SSLError) as soe: + uplink.Logs.critical(f"[SSL ERROR]: {soe}") + except OSError as oe: + uplink.Logs.critical(f"[OS Error]: {oe}") + if 'connection refused' in str(oe).lower(): + sys.exit(oe) + except AttributeError as ae: + uplink.Logs.critical(f"AttributeError: {ae}") + def run_python_garbage_collector() -> int: """Run Python garbage collector @@ -131,3 +179,19 @@ def clean_uid(uid: str) -> Optional[str]: parsed_UID = sub(pattern, '', uid) return parsed_UID + +def hide_sensitive_data(srvmsg: list[str]) -> list[str]: + try: + srv_msg = srvmsg.copy() + privmsg_index = srv_msg.index('PRIVMSG') + auth_index = privmsg_index + 2 + if match(r'^:{1}\W?(auth)$', srv_msg[auth_index]) is None: + return srv_msg + + for l in range(auth_index + 1, len(srv_msg)): + srv_msg[l] = '*' * len(srv_msg[l]) + + return srv_msg + + except ValueError: + return srvmsg \ No newline at end of file diff --git a/mods/clone/utils.py b/mods/clone/utils.py index ce509a1..e2b5759 100644 --- a/mods/clone/utils.py +++ b/mods/clone/utils.py @@ -177,7 +177,7 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]): uid_sender = uplink.Irc.Utils.clean_uid(srvmsg[1]) - senderObj = uplink.User.get_User(uid_sender) + senderObj = uplink.User.get_user(uid_sender) if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT: return diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index 75d77c6..fbab102 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -277,7 +277,7 @@ class Command: return None user_uid = self.User.clean_uid(cmd[5]) - userObj: MUser = self.User.get_User(user_uid) + userObj: MUser = self.User.get_user(user_uid) channel_name = cmd[4] if self.Channel.is_valid_channel(cmd[4]) else None client_obj = self.Client.get_Client(user_uid) nickname = userObj.nickname if userObj is not None else None @@ -291,12 +291,13 @@ class Command: if 'r' not in userObj.umodes and 'o' not in userObj.umodes and not self.Client.is_exist(userObj.uid): return None - db_data: dict[str, str] = {"nickname": nickname, "channel": channel_name} - db_query = self.Base.db_execute_query("SELECT id, mode FROM command_automode WHERE nickname = :nickname AND channel = :channel", db_data) + db_data: dict[str, str] = {"nickname": nickname.lower(), "channel": channel_name.lower()} + db_query = self.Base.db_execute_query("SELECT id, mode FROM command_automode WHERE LOWER(nickname) = :nickname AND LOWER(channel) = :channel", db_data) db_result = db_query.fetchone() - if db_result is not None: + if db_result: id, mode = db_result self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {channel_name} {mode} {userObj.nickname}") + except KeyError as ke: self.Logs.error(f"Key Error: {err}") @@ -477,15 +478,18 @@ class Command: try: self.mod_utils.set_assign_channel_to_service(self, cmd, fromuser) except IndexError as ie: + self.Logs.debug(f'{ie}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'part' | 'unassign': try: + # Syntax. !part #channel self.mod_utils.set_unassign_channel_to_service(self, cmd, fromuser) except IndexError as ie: - self.Logs.error(f'{ie}') + self.Logs.debug(f'{ie}') + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') @@ -668,7 +672,7 @@ class Command: nickname = str(cmd[1]) umode = str(cmd[2]) - self.Protocol.send_svs_mode(nickname=nickname, user_mode=umode) + self.Protocol.send_svsmode(nickname=nickname, user_mode=umode) except KeyError as ke: self.Logs.error(ke) except Exception as err: @@ -715,34 +719,64 @@ class Command: case 'svsjoin': try: - # .svsjoin nickname #channel - nickname = str(cmd[1]) - channel = str(cmd[2]) - if len(cmd) != 3: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN nickname #channel") + # SVSJOIN [,..] [key1[,key2[..]]] + if len(cmd) < 4: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN [,..] [key1[,key2[..]]]") return None - self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSJOIN {nickname} {channel}') - except KeyError as ke: + nickname = str(cmd[1]) + channels = str(cmd[2]).split(',') + keys = str(cmd[3]).split(',') + + self.Protocol.send_svsjoin(nickname, channels, keys) + except IndexError as ke: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN [,..] [key1[,key2[..]]]") self.Logs.error(ke) except Exception as err: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN nickname #channel") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN [,..] [key1[,key2[..]]]") self.Logs.warning(f'Unknown Error: {str(err)}') case 'svspart': try: - # svspart nickname #channel - nickname = str(cmd[1]) - channel = str(cmd[2]) - if len(cmd) != 3: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART nickname #channel") + # SVSPART [,..] [] + if len(cmd) < 4: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART [,..] []") return None - self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSPART {nickname} {channel}') - except KeyError as ke: + nickname = str(cmd[1]) + channels = str(cmd[2]).split(',') + reason = ' '.join(cmd[3:]) + + self.Protocol.send_svspart(nickname, channels, reason) + except IndexError as ke: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART [,..] []") self.Logs.error(ke) except Exception as err: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART nickname #channel") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART [,..] []") + self.Logs.warning(f'Unknown Error: {str(err)}') + + case 'svsnick': + try: + # .svsnick nickname newnickname + nickname = str(cmd[1]) + newnickname = str(cmd[2]) + unixtime = self.MainUtils.get_unixtime() + + if self.User.get_nickname(nickname) is None: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" This nickname do not exist") + return None + + if len(cmd) != 3: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname") + return None + + self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSNICK {nickname} {newnickname} {unixtime}') + + except IndexError as ke: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname") + self.Logs.error(ke) + except Exception as err: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname") self.Logs.warning(f'Unknown Error: {str(err)}') case 'sajoin': @@ -779,29 +813,6 @@ class Command: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel") self.Logs.error(f'Unknown Error: {str(err)}') - case 'svsnick': - try: - # .svsnick nickname newnickname - nickname = str(cmd[1]) - newnickname = str(cmd[2]) - unixtime = self.MainUtils.get_unixtime() - - if self.User.get_nickname(nickname) is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" This nickname do not exist") - return None - - if len(cmd) != 3: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname") - return None - - self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSNICK {nickname} {newnickname} {unixtime}') - - except KeyError as ke: - self.Logs.error(ke) - except Exception as err: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname") - self.Logs.warning(f'Unknown Error: {str(err)}') - case 'kill': try: # 'kill', 'gline', 'ungline', 'shun', 'unshun' diff --git a/mods/command/utils.py b/mods/command/utils.py index c01e70b..b7a40dc 100644 --- a/mods/command/utils.py +++ b/mods/command/utils.py @@ -201,6 +201,9 @@ def set_kickban(uplink: 'Command', cmd: list[str], client: str) -> None: def set_assign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None: + if len(cmd) < 2: + raise IndexError(f"{cmd[0].upper()} is expecting the channel parameter") + command = str(cmd[0]) dnickname = uplink.Config.SERVICE_NICKNAME sent_channel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None @@ -217,6 +220,9 @@ def set_assign_channel_to_service(uplink: 'Command', cmd: list[str], client: str def set_unassign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None: + if len(cmd) < 2: + raise IndexError(f"{cmd[0].upper()} is expecting the channel parameter") + command = str(cmd[0]) dnickname = uplink.Config.SERVICE_NICKNAME dchanlog = uplink.Config.SERVICE_CHANLOG diff --git a/mods/defender/mod_defender.py b/mods/defender/mod_defender.py index d12ea8a..91a7c32 100644 --- a/mods/defender/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -273,11 +273,12 @@ class Defender: return None def cmd(self, data: list[str]) -> None: - try: - if not data or len(data) < 2: - return None - cmd = data.copy() if isinstance(data, list) else list(data).copy() + if not data or len(data) < 2: + return None + cmd = data.copy() if isinstance(data, list) else list(data).copy() + + try: index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd) if index == -1: return None @@ -398,7 +399,7 @@ class Defender: self.Protocol.send_sapart(nick_to_sapart=jailed_nickname, channel_name=jailed_salon) self.Protocol.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=welcome_salon) self.Protocol.send2socket(f":{link} REPUTATION {jailed_IP} {self.ModConfig.reputation_score_after_release}") - self.User.get_User(jailed_UID).score_connexion = reputation_seuil + 1 + self.User.get_user(jailed_UID).score_connexion = reputation_seuil + 1 self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{color_green} MOT DE PASS CORRECT {color_black}] : You have now the right to enjoy the network !", nick_to=jailed_nickname) @@ -547,7 +548,7 @@ class Defender: link = self.Config.SERVEUR_LINK jailed_salon = self.Config.SALON_JAIL welcome_salon = self.Config.SALON_LIBERER - client_obj = self.User.get_User(str(cmd[2])) + client_obj = self.User.get_user(str(cmd[2])) if client_obj is None: p.send_notice(nick_from=dnickname, @@ -936,7 +937,7 @@ class Defender: return None nickoruid = cmd[1] - UserObject = self.User.get_User(nickoruid) + UserObject = self.User.get_user(nickoruid) if UserObject is not None: channels: list = [chan.name for chan in self.Channel.UID_CHANNEL_DB for uid_in_chan in chan.uids if self.Loader.Utils.clean_uid(uid_in_chan) == UserObject.uid] diff --git a/mods/defender/utils.py b/mods/defender/utils.py index 9855791..bf85d0d 100644 --- a/mods/defender/utils.py +++ b/mods/defender/utils.py @@ -213,7 +213,7 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]): return None # Get User information - _User = irc.User.get_User(str(srvmsg[8])) + _User = irc.User.get_user(str(srvmsg[8])) if _User is None: irc.Logs.warning(f'This UID: [{srvmsg[8]}] is not available please check why') @@ -263,7 +263,7 @@ def action_on_flood(uplink: 'Defender', srvmsg: list[str]): user_trigger = str(srvmsg[1]).replace(':','') channel = srvmsg[3] - User = irc.User.get_User(user_trigger) + User = irc.User.get_user(user_trigger) if User is None or not irc.Channel.is_valid_channel(channel_to_check=channel): return diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index 54acd2d..0f7c989 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -318,7 +318,7 @@ class Votekick: nickname_submitted = cmd[2] uid_submitted = self.User.get_uid(nickname_submitted) - user_submitted = self.User.get_User(nickname_submitted) + user_submitted = self.User.get_user(nickname_submitted) ongoing_user = None # check if there is an ongoing vote diff --git a/mods/votekick/votekick_manager.py b/mods/votekick/votekick_manager.py index 1eb7098..d61fed0 100644 --- a/mods/votekick/votekick_manager.py +++ b/mods/votekick/votekick_manager.py @@ -144,7 +144,7 @@ class VotekickManager: votec = self.get_vote_channel_model(channel_name) if votec: - client_obj = self.uplink.User.get_User(votec.target_user) + client_obj = self.uplink.User.get_user(votec.target_user) client_to_punish = votec.target_user if client_obj is None else client_obj.nickname if nickname in votec.voter_users: self.Logs.debug(f"[VOTEKICK MANAGER] This nickname ({nickname}) has already voted for ({client_to_punish})") diff --git a/version.json b/version.json index 13be7c0..a4e67ca 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "6.2.0", + "version": "6.2.1", "requests": "2.32.3", "psutil": "6.0.0",