From e79c15188eaff27dab3d6c8e34b2ff643964c514 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 30 Aug 2025 23:09:03 +0200 Subject: [PATCH 1/6] Quick updates: - Set default language for admins when running the db patch - Updating addaccess command. - Update levels for some commands in mod_command. --- core/base.py | 2 + core/classes/admin.py | 19 ++++++++ core/irc.py | 89 +++++++++++++++++++------------------ mods/command/mod_command.py | 20 ++++----- version.json | 2 +- 5 files changed, 78 insertions(+), 54 deletions(-) diff --git a/core/base.py b/core/base.py index 9fcb0f5..9f1f879 100644 --- a/core/base.py +++ b/core/base.py @@ -600,7 +600,9 @@ class Base: def db_patch(self, table_name: str, column_name: str, column_type: str) -> bool: if not self.db_is_column_exist(table_name, column_name): patch = f"ALTER TABLE {self.Config.TABLE_ADMIN} ADD COLUMN {column_name} {column_type}" + update_row = f"UPDATE {self.Config.TABLE_ADMIN} SET language = 'EN' WHERE language is null" self.db_execute_query(patch) + self.db_execute_query(update_row) self.logs.debug(f"The patch has been applied") self.logs.debug(f"Table name: {table_name}, Column name: {column_name}, Column type: {column_type}") return True diff --git a/core/classes/admin.py b/core/classes/admin.py index aa4da1c..5ff95d8 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -201,3 +201,22 @@ class Admin: return True return False + + def db_is_admin_exist(self, admin_nickname: str) -> bool: + """Verify if the admin exist in the database! + + Args: + admin_nickname (str): The nickname admin to check. + + Returns: + bool: True if the admin exist otherwise False. + """ + + mes_donnees = {'admin': admin_nickname} + query_search_user = f"SELECT id FROM {self.Config.TABLE_ADMIN} WHERE user = :admin" + r = self.Base.db_execute_query(query_search_user, mes_donnees) + exist_user = r.fetchone() + if exist_user: + return True + else: + return False diff --git a/core/irc.py b/core/irc.py index 957d7aa..19676f1 100644 --- a/core/irc.py +++ b/core/irc.py @@ -409,57 +409,58 @@ class Irc: return None - def create_defender_user(self, nickname: str, level: int, password: str) -> str: + def create_defender_user(self, sender: str, new_admin: str, new_level: int, new_password: str) -> bool: + """Create a new admin user for defender + + Args: + sender (str): The current admin sending the request + new_admin (str): The new admin to create + new_level (int): The level of the admin + new_password (str): The clear password + + Returns: + bool: True if created. + """ # > addaccess [nickname] [level] [password] + dnick = self.Config.SERVICE_NICKNAME + p = self.Protocol - get_user = self.User.get_user(nickname) - level = self.Base.convert_to_int(level) - password = password + get_user = self.User.get_user(new_admin) + level = self.Base.convert_to_int(new_level) + password = new_password if get_user is None: - response = f'This nickname {nickname} does not exist, it is not possible to create this user' - self.Logs.warning(response) - return response + response = tr("The nickname (%s) is not currently connected! please create a new admin when the nickname is connected to the network!", new_admin) + p.send_notice(dnick, sender, response) + self.Logs.debug(f"New admin {new_admin} sent by {sender} is not connected") + return False - if level is None: - response = f'The level [{level}] must be a number from 1 to 4' - self.Logs.warning(response) - return response - - if level > 4: - response = "Impossible d'ajouter un niveau > 4" - self.Logs.warning(response) - return response + if level is None or level > 4 or level == 0: + p.send_notice(dnick, sender, tr("The level (%s) must be a number from 1 to 4", level)) + self.Logs.debug(f"Level must a number between 1 to 4 (sent by {sender})") + return False nickname = get_user.nickname - response = '' - hostname = get_user.hostname vhost = get_user.vhost spassword = self.Loader.Utils.hash_password(password) - mes_donnees = {'admin': nickname} - query_search_user = f"SELECT id FROM {self.Config.TABLE_ADMIN} WHERE user=:admin" - r = self.Base.db_execute_query(query_search_user, mes_donnees) - exist_user = r.fetchone() - - # On verifie si le user exist dans la base - if not exist_user: - mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level} + # Check if the user already exist + if not self.Admin.db_is_admin_exist(nickname): + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level, 'language': 'EN'} self.Base.db_execute_query(f'''INSERT INTO {self.Config.TABLE_ADMIN} - (createdOn, user, password, hostname, vhost, level) VALUES - (:datetime, :user, :password, :hostname, :vhost, :level) + (createdOn, user, password, hostname, vhost, level, language) VALUES + (:datetime, :user, :password, :hostname, :vhost, :level, :language) ''', mes_donnees) - response = f"{nickname} ajouté en tant qu'administrateur de niveau {level}" - self.Protocol.send_notice(nick_from=self.Config.SERVICE_NICKNAME, nick_to=nickname, msg=response) - self.Logs.info(response) - return response + + p.send_notice(dnick, sender, tr("New admin (%s) has been added with level %s", nickname, level)) + self.Logs.info(f"A new admin ({nickname}) has been created by {sender}!") + return True else: - response = f'{nickname} Existe déjà dans les users enregistrés' - self.Protocol.send_notice(nick_from=self.Config.SERVICE_NICKNAME, nick_to=nickname, msg=response) - self.Logs.info(response) - return response + p.send_notice(dnick, sender, tr("The nickname (%s) Already exist!", nickname)) + self.Logs.info(f"The nickname {nickname} already exist! (sent by {sender})") + return False def thread_check_for_new_version(self, fromuser: str) -> None: dnickname = self.Config.SERVICE_NICKNAME @@ -681,6 +682,9 @@ class Irc: channel=dchanlog ) + self.Protocol.send_notice(dnickname, fromuser, tr("You have been successfully disconnected from %s", dnickname)) + return None + case 'firstauth': # firstauth OWNER_NICKNAME OWNER_PASSWORD current_nickname = self.User.get_nickname(fromuser) @@ -818,15 +822,14 @@ class Irc: if len(cmd) < 4: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Right command : /msg {dnickname} addaccess [nickname] [level] [password]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"level: from 1 to 4") + return None - newnickname = cmd[1] - newlevel = self.Base.int_if_possible(cmd[2]) - password = cmd[3] + new_admin = str(cmd[1]) + level = self.Base.int_if_possible(cmd[2]) + password = str(cmd[3]) - response = self.create_defender_user(newnickname, newlevel, password) - - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{response}") - self.Logs.info(response) + self.create_defender_user(fromuser, new_admin, level, password) + return None except IndexError as ie: self.Logs.error(f'_hcmd addaccess: {ie}') diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index cac7a59..d125914 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -53,16 +53,16 @@ class Command: # Module Utils self.mod_utils = utils - self.Irc.build_command(1, self.module_name, 'join', 'Join a channel') - self.Irc.build_command(1, self.module_name, 'assign', 'Assign a user to a role or task') - self.Irc.build_command(1, self.module_name, 'part', 'Leave a channel') - self.Irc.build_command(1, self.module_name, 'unassign', 'Remove a user from a role or task') - self.Irc.build_command(1, self.module_name, 'owner', 'Give channel ownership to a user') - self.Irc.build_command(1, self.module_name, 'deowner', 'Remove channel ownership from a user') - self.Irc.build_command(1, self.module_name, 'protect', 'Protect a user from being kicked') - self.Irc.build_command(1, self.module_name, 'deprotect', 'Remove protection from a user') - self.Irc.build_command(1, self.module_name, 'op', 'Grant operator privileges to a user') - self.Irc.build_command(1, self.module_name, 'deop', 'Remove operator privileges from a user') + self.Irc.build_command(2, self.module_name, 'join', 'Join a channel') + self.Irc.build_command(2, self.module_name, 'assign', 'Assign a user to a role or task') + self.Irc.build_command(2, self.module_name, 'part', 'Leave a channel') + self.Irc.build_command(2, self.module_name, 'unassign', 'Remove a user from a role or task') + self.Irc.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user') + self.Irc.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user') + self.Irc.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked') + self.Irc.build_command(2, self.module_name, 'deprotect', 'Remove protection from a user') + self.Irc.build_command(2, self.module_name, 'op', 'Grant operator privileges to a user') + self.Irc.build_command(2, self.module_name, 'deop', 'Remove operator privileges from a user') self.Irc.build_command(1, self.module_name, 'halfop', 'Grant half-operator privileges to a user') self.Irc.build_command(1, self.module_name, 'dehalfop', 'Remove half-operator privileges from a user') self.Irc.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user') diff --git a/version.json b/version.json index 5474841..3a5286e 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "6.2.5", + "version": "6.2.6", "requests": "2.32.3", "psutil": "6.0.0", From 6b7fd16a44d3d6284616407304c4b9d445597429 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Wed, 3 Sep 2025 22:01:52 +0200 Subject: [PATCH 2/6] Connectecting to inspircd --- .gitignore | 2 + core/base.py | 5 +- core/classes/protocol.py | 19 -- core/classes/protocols/factory.py | 28 ++ core/classes/protocols/inspircd.py | 68 ++++- core/classes/protocols/interface.py | 447 ++++++++++++++++++++++++++++ core/classes/protocols/unreal6.py | 23 +- core/classes/rehash.py | 10 +- core/irc.py | 36 +-- core/loader.py | 6 + core/logs.py | 2 +- core/utils.py | 8 +- defender.py | 13 +- 13 files changed, 585 insertions(+), 82 deletions(-) delete mode 100644 core/classes/protocol.py create mode 100644 core/classes/protocols/factory.py create mode 100644 core/classes/protocols/interface.py diff --git a/.gitignore b/.gitignore index 2428eb1..7977a52 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,7 @@ db/ logs/ __pycache__/ configuration.json +configuration_inspircd.json +configuration_unreal6.json *.log test.py \ No newline at end of file diff --git a/core/base.py b/core/base.py index 9f1f879..2755125 100644 --- a/core/base.py +++ b/core/base.py @@ -683,14 +683,13 @@ class Base: return False def decode_ip(self, ip_b64encoded: str) -> Optional[str]: - - binary_ip = b64decode(ip_b64encoded) try: + binary_ip = b64decode(ip_b64encoded) decoded_ip = ipaddress.ip_address(binary_ip) return decoded_ip.exploded except ValueError as ve: - self.logs.critical(f'This remote ip is not valid : {ve}') + self.logs.critical(f'This remote ip ({ip_b64encoded}) is not valid : {ve}') return None def encode_ip(self, remote_ip_address: str) -> Optional[str]: diff --git a/core/classes/protocol.py b/core/classes/protocol.py deleted file mode 100644 index e20ff20..0000000 --- a/core/classes/protocol.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Literal, TYPE_CHECKING -from .protocols.unreal6 import Unrealircd6 -from .protocols.inspircd import Inspircd - -if TYPE_CHECKING: - from core.irc import Irc - -class Protocol: - - def __init__(self, protocol: Literal['unreal6','inspircd'], ircInstance: 'Irc'): - - self.Protocol = None - match protocol: - case 'unreal6': - self.Protocol: Unrealircd6 = Unrealircd6(ircInstance) - case 'inspircd': - self.Protocol: Inspircd = Inspircd(ircInstance) - case _: - self.Protocol: Unrealircd6 = Unrealircd6(ircInstance) diff --git a/core/classes/protocols/factory.py b/core/classes/protocols/factory.py new file mode 100644 index 0000000..32ac63a --- /dev/null +++ b/core/classes/protocols/factory.py @@ -0,0 +1,28 @@ +from typing import TYPE_CHECKING, Optional +from .unreal6 import Unrealircd6 +from .inspircd import Inspircd +from .interface import IProtocol + +if TYPE_CHECKING: + from core.irc import Irc + +class ProtocolFactorty: + + def __init__(self, uplink: 'Irc'): + self.__Config = uplink.Config + self.__uplink = uplink + + def get(self) -> Optional[IProtocol]: + + protocol = self.__Config.SERVEUR_PROTOCOL + + match protocol: + case 'unreal6': + self.__uplink.Logs.debug(f"[PROTOCOL] {protocol} has been loaded") + return Unrealircd6(self.__uplink) + case 'inspircd': + self.__uplink.Logs.debug(f"[PROTOCOL] {protocol} has been loaded") + return Inspircd(self.__uplink) + case _: + self.__uplink.Logs.critical(f"[PROTOCOL ERROR] This protocol name ({protocol} is not valid!)") + raise Exception("Unknown protocol!") diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index 33debaa..d41a9db 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -1,6 +1,7 @@ from re import match, findall from datetime import datetime -from typing import TYPE_CHECKING +import sys +from typing import TYPE_CHECKING, Optional from ssl import SSLEOFError, SSLError if TYPE_CHECKING: @@ -17,8 +18,32 @@ class Inspircd: self.__Utils = ircInstance.Loader.Utils self.__Logs = ircInstance.Loader.Logs + self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', + 'EOS', 'PRIVMSG', 'MODE', 'UMODE2', + 'VERSION', 'REPUTATION', 'SVS2MODE', + 'SLOG', 'NICK', 'PART', 'PONG', 'SASL', 'PING', + 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO', + '006', '007', '018'} + self.__Logs.info(f"** Loading protocol [{__name__}]") + def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + """Get the position of known commands + + Args: + cmd (list[str]): The server response + + Returns: + tuple[int, Optional[str]]: The position and the command. + """ + for index, token in enumerate(cmd): + if token.upper() in self.known_protocol: + return index, token.upper() + + self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") + + return (-1, None) + def send2socket(self, message: str, print_log: bool = True) -> None: """Envoit les commandes à envoyer au serveur. @@ -45,6 +70,8 @@ class Inspircd: self.__Logs.error(f"SSLError: {se} - {message}") except OSError as oe: self.__Logs.error(f"OSError: {oe} - {message}") + if oe.errno == 10053: + sys.exit(oe) except AttributeError as ae: self.__Logs.critical(f"Attribute Error: {ae}") @@ -175,7 +202,8 @@ class Inspircd: self.__Logs.error(f"The channel [{channel}] is not valid") return None - self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Utils.get_unixtime()} {channel} + :{self.__Config.SERVICE_ID}") + # self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Utils.get_unixtime()} {channel} + :{self.__Config.SERVICE_ID}") + self.send2socket(f":{self.__Config.SERVICE_ID} FJOIN {channel} 68") # Add defender to the channel uids list self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[self.__Config.SERVICE_ID])) @@ -481,32 +509,29 @@ class Inspircd: def on_uid(self, serverMsg: list[str]) -> None: """Handle uid message coming from the server - + [:] UID []+ : + [':97K', 'UID', '97KAAAAAB', '1756928055', 'adator_', '172.18.128.1', '172.18.128.1', '...', '...', '172.18.128.1', '1756928055', '+', ':...'] Args: serverMsg (list[str]): Original server message """ - # ['@s2s-md/geoip=cc=GB|cd=United\\sKingdom|asn=16276|asname=OVH\\sSAS;s2s-md/tls_cipher=TLSv1.3-TLS_CHACHA20_POLY1305_SHA256;s2s-md/creationtime=1721564601', - # ':001', 'UID', 'albatros', '0', '1721564597', 'albatros', 'vps-91b2f28b.vps.ovh.net', - # '001HB8G04', '0', '+iwxz', 'Clk-A62F1D18.vps.ovh.net', 'Clk-A62F1D18.vps.ovh.net', 'MyZBwg==', ':...'] try: - isWebirc = True if 'webirc' in serverMsg[0] else False isWebsocket = True if 'websocket' in serverMsg[0] else False - uid = str(serverMsg[8]) - nickname = str(serverMsg[3]) - username = str(serverMsg[6]) - hostname = str(serverMsg[7]) - umodes = str(serverMsg[10]) - vhost = str(serverMsg[11]) + uid = str(serverMsg[2]) + nickname = str(serverMsg[4]) + username = str(serverMsg[7]) + hostname = str(serverMsg[5]) + umodes = str(serverMsg[11]) + vhost = str(serverMsg[6]) if not 'S' in umodes: - remote_ip = self.__Base.decode_ip(str(serverMsg[13])) + remote_ip = self.__Base.decode_ip(str(serverMsg[9])) else: remote_ip = '127.0.0.1' # extract realname - realname = ' '.join(serverMsg[14:]).lstrip(':') + realname = ' '.join(serverMsg[12:]).lstrip(':') # Extract Geoip information pattern = r'^.*geoip=cc=(\S{2}).*$' @@ -540,7 +565,7 @@ class Inspircd: except IndexError as ie: self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}", exc_info=True) def on_server_ping(self, serverMsg: list[str]) -> None: """Send a PONG message to the server @@ -560,6 +585,17 @@ class Inspircd: except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") + def on_server(self, serverMsg: list[str]) -> None: + """_summary_ + + Args: + serverMsg (list[str]): _description_ + """ + try: + ... + except Exception as err: + self.__Logs.error(f'General Error: {err}') + def on_version(self, serverMsg: list[str]) -> None: """Sending Server Version to the server diff --git a/core/classes/protocols/interface.py b/core/classes/protocols/interface.py new file mode 100644 index 0000000..17005c9 --- /dev/null +++ b/core/classes/protocols/interface.py @@ -0,0 +1,447 @@ +from abc import ABC, abstractmethod +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from core.classes.sasl import Sasl + from core.definition import MClient, MSasl + +class IProtocol(ABC): + + @abstractmethod + def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + """Get the position of known commands + + Args: + cmd (list[str]): The server response + + Returns: + tuple[int, Optional[str]]: The position and the command. + """ + + @abstractmethod + def send2socket(self, message: str, print_log: bool = True) -> None: + """Envoit les commandes à envoyer au serveur. + + Args: + string (Str): contient la commande à envoyer au serveur. + """ + + @abstractmethod + def send_priv_msg(self, nick_from: str, msg: str, channel: str = None, nick_to: str = None): + """Sending PRIVMSG to a channel or to a nickname by batches + could be either channel or nickname not both together + Args: + msg (str): The message to send + nick_from (str): The sender nickname + channel (str, optional): The receiver channel. Defaults to None. + nick_to (str, optional): The reciever nickname. Defaults to None. + """ + + @abstractmethod + def send_notice(self, nick_from: str, nick_to: str, msg: str) -> None: + """Sending NOTICE by batches + + Args: + msg (str): The message to send to the server + nick_from (str): The sender Nickname + nick_to (str): The reciever nickname + """ + + @abstractmethod + def send_link(self) -> None: + """Créer le link et envoyer les informations nécessaires pour la + connexion au serveur. + """ + + @abstractmethod + def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + hostname (str): _description_ + set_by (str): _description_ + expire_timestamp (int): _description_ + set_at_timestamp (int): _description_ + reason (str): _description_ + """ + + @abstractmethod + def send_set_nick(self, newnickname: str) -> None: + """Change nickname of the server + \n This method will also update the User object + Args: + newnickname (str): New nickname of the server + """ + + @abstractmethod + def send_squit(self, server_id: str, server_link: str, reason: str) -> None: + """_summary_ + + Args: + server_id (str): _description_ + server_link (str): _description_ + reason (str): _description_ + """ + + @abstractmethod + def send_ungline(self, nickname:str, hostname: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + hostname (str): _description_ + """ + + @abstractmethod + def send_kline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + hostname (str): _description_ + set_by (str): _description_ + expire_timestamp (int): _description_ + set_at_timestamp (int): _description_ + reason (str): _description_ + """ + + @abstractmethod + def send_unkline(self, nickname:str, hostname: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + hostname (str): _description_ + """ + + @abstractmethod + def send_sjoin(self, channel: str) -> None: + """Server will join a channel with pre defined umodes + + Args: + channel (str): Channel to join + """ + + @abstractmethod + def send_sapart(self, nick_to_sapart: str, channel_name: str) -> None: + """_summary_ + + Args: + from_nick (str): _description_ + nick_to (str): _description_ + channel_name (str): _description_ + """ + + @abstractmethod + def send_sajoin(self, nick_to_sajoin: str, channel_name: str) -> None: + """_summary_ + + Args: + nick_to_sajoin (str): _description_ + channel_name (str): _description_ + """ + + @abstractmethod + def send_svspart(self, nick_to_part: str, channels: list[str], reason: str) -> None: + """_summary_ + + Args: + nick_to_part (str): _description_ + channels (list[str]): _description_ + reason (str): _description_ + """ + + @abstractmethod + def send_svsjoin(self, nick_to_part: str, channels: list[str], keys: list[str]) -> None: + """_summary_ + + Args: + nick_to_part (str): _description_ + channels (list[str]): _description_ + keys (list[str]): _description_ + """ + + @abstractmethod + def send_svsmode(self, nickname: str, user_mode: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + user_mode (str): _description_ + """ + + @abstractmethod + def send_svs2mode(self, nickname: str, user_mode: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + user_mode (str): _description_ + """ + + @abstractmethod + 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 + """ + + @abstractmethod + def send_svslogout(self, client_obj: 'MClient') -> None: + """Logout a client from his account + + Args: + client_uid (str): The Client UID + """ + + @abstractmethod + def send_quit(self, uid: str, reason: str, print_log: True) -> None: + """Send quit message + - Delete uid from User object + - Delete uid from Reputation object + + Args: + uidornickname (str): The UID or the Nickname + reason (str): The reason for the quit + """ + + @abstractmethod + def send_uid(self, nickname:str, username: str, hostname: str, uid:str, umodes: str, vhost: str, remote_ip: str, realname: str, print_log: bool = True) -> None: + """Send UID to the server + - Insert User to User Object + Args: + nickname (str): Nickname of the client + username (str): Username of the client + hostname (str): Hostname of the client you want to create + uid (str): UID of the client you want to create + umodes (str): umodes of the client you want to create + vhost (str): vhost of the client you want to create + remote_ip (str): remote_ip of the client you want to create + realname (str): realname of the client you want to create + print_log (bool, optional): print logs if true. Defaults to True. + """ + + @abstractmethod + def send_join_chan(self, uidornickname: str, channel: str, password: str = None, print_log: bool = True) -> None: + """Joining a channel + + Args: + uidornickname (str): UID or nickname that need to join + channel (str): channel to join + password (str, optional): The password of the channel to join. Default to None + print_log (bool, optional): Write logs. Defaults to True. + """ + + @abstractmethod + def send_part_chan(self, uidornickname:str, channel: str, print_log: bool = True) -> None: + """Part from a channel + + Args: + uidornickname (str): UID or nickname that need to join + channel (str): channel to join + print_log (bool, optional): Write logs. Defaults to True. + """ + + @abstractmethod + def send_mode_chan(self, channel_name: str, channel_mode: str) -> None: + """_summary_ + + Args: + channel_name (str): _description_ + channel_mode (str): _description_ + """ + + @abstractmethod + def send_raw(self, raw_command: str) -> None: + """_summary_ + + Args: + raw_command (str): _description_ + """ + + ##################### + # HANDLE EVENTS # + ##################### + + @abstractmethod + def on_svs2mode(self, serverMsg: list[str]) -> None: + """Handle svs2mode coming from a server + >>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r'] + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_mode(self, serverMsg: list[str]) -> None: + """Handle mode coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_umode2(self, serverMsg: list[str]) -> None: + """Handle umode2 coming from a server + >>> [':adator_', 'UMODE2', '-i'] + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_quit(self, serverMsg: list[str]) -> None: + """Handle quit coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_squit(self, serverMsg: list[str]) -> None: + """Handle squit coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_protoctl(self, serverMsg: list[str]) -> None: + """Handle protoctl coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_nick(self, serverMsg: list[str]) -> None: + """Handle nick coming from a server + new nickname + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_sjoin(self, serverMsg: list[str]) -> None: + """Handle sjoin coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_part(self, serverMsg: list[str]) -> None: + """Handle part coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_eos(self, serverMsg: list[str]) -> None: + """Handle EOS coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_reputation(self, serverMsg: list[str]) -> None: + """Handle REPUTATION coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_uid(self, serverMsg: list[str]) -> None: + """Handle uid message coming from the server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_privmsg(self, serverMsg: list[str]) -> None: + """Handle PRIVMSG message coming from the server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_server_ping(self, serverMsg: list[str]) -> None: + """Send a PONG message to the server + + Args: + serverMsg (list[str]): List of str coming from the server + """ + + @abstractmethod + def on_server(self, serverMsg: list[str]) -> None: + """_summary_ + + Args: + serverMsg (list[str]): _description_ + """ + + @abstractmethod + def on_version(self, serverMsg: list[str]) -> None: + """Sending Server Version to the server + + Args: + serverMsg (list[str]): List of str coming from the server + """ + + @abstractmethod + def on_time(self, serverMsg: list[str]) -> None: + """Sending TIME answer to a requestor + + Args: + serverMsg (list[str]): List of str coming from the server + """ + + @abstractmethod + def on_ping(self, serverMsg: list[str]) -> None: + """Sending a PING answer to requestor + + Args: + serverMsg (list[str]): List of str coming from the server + """ + + @abstractmethod + def on_version_msg(self, serverMsg: list[str]) -> None: + """Handle version coming from the server + \n ex. /version Defender + Args: + serverMsg (list[str]): Original message from the server + """ + + @abstractmethod + def on_smod(self, serverMsg: list[str]) -> None: + """Handle SMOD message coming from the server + + Args: + serverMsg (list[str]): Original server message + """ + + @abstractmethod + def on_sasl(self, serverMsg: list[str], psasl: 'Sasl') -> Optional['MSasl']: + """Handle SASL coming from a server + + Args: + serverMsg (list[str]): Original server message + psasl (Sasl): The SASL process object + """ + + @abstractmethod + def on_md(self, serverMsg: list[str]) -> None: + """Handle MD responses + [':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...'] + Args: + serverMsg (list[str]): The server reply + """ diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index 14c7542..33581bb 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import TYPE_CHECKING, Optional from ssl import SSLEOFError, SSLError +from core.classes.protocols.interface import IProtocol from core.utils import tr if TYPE_CHECKING: @@ -11,7 +12,7 @@ if TYPE_CHECKING: from core.classes.sasl import Sasl from core.definition import MClient, MSasl -class Unrealircd6: +class Unrealircd6(IProtocol): def __init__(self, ircInstance: 'Irc'): self.name = 'UnrealIRCD-6' @@ -200,6 +201,7 @@ class Unrealircd6: self.send2socket(f":{server_id} PROTOCTL SID={server_id}") self.send2socket(f":{server_id} PROTOCTL BOOTED={unixtime}") self.send2socket(f":{server_id} SERVER {link} 1 :{info}") + self.send2socket("EOS") self.send2socket(f":{server_id} {nickname} :Reserved for services") self.send2socket(f":{server_id} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * fwAAAQ== :{realname}") self.send_sjoin(chan) @@ -1035,15 +1037,6 @@ 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) @@ -1106,7 +1099,6 @@ class Unrealircd6: serverMsg (list[str]): List of str coming from the server """ try: - # pong = str(serverMsg[1]).replace(':','') self.send2socket(f"PONG :{pong}", print_log=False) @@ -1115,6 +1107,11 @@ class Unrealircd6: self.__Logs.error(f"{__name__} - General Error: {err}") def on_server(self, serverMsg: list[str]) -> None: + """_summary_ + + Args: + serverMsg (list[str]): _description_ + """ try: # ['SERVER', 'irc.local.org', '1', ':U6100-Fhn6OoE-001', 'Local', 'Server'] sCopy = serverMsg.copy() @@ -1177,7 +1174,7 @@ class Unrealircd6: Args: serverMsg (list[str]): List of str coming from the server """ - # ['@unrealircd.org/userhost=StatServ@stats.deb.biz.st;draft/bot;bot;msgid=ehfAq3m2yjMjhgWEfi1UCS;time=2024-10-26T13:49:06.299Z', ':001INC60B', 'PRIVMSG', '12ZAAAAAB', ':\x01PING', '762382207\x01'] + # ['@unrealircd.org/...', ':001INC60B', 'PRIVMSG', '12ZAAAAAB', ':\x01PING', '762382207\x01'] # Réponse a un CTCP VERSION try: @@ -1186,6 +1183,7 @@ class Unrealircd6: arg = serverMsg[4].replace(':', '') if nickname is None: + self.__Logs.debug(serverMsg) return None if arg == '\x01PING': @@ -1199,6 +1197,7 @@ class Unrealircd6: nick_to=nickname, msg=f"\x01PING {ping_response} secs\x01" ) + self.__Logs.debug(serverMsg) return None except Exception as err: diff --git a/core/classes/rehash.py b/core/classes/rehash.py index bc7dfa6..72a2465 100644 --- a/core/classes/rehash.py +++ b/core/classes/rehash.py @@ -3,7 +3,6 @@ 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 @@ -15,9 +14,10 @@ REHASH_MODULES = [ 'core.classes.config', 'core.base', 'core.classes.commands', + 'core.classes.protocols.interface', + 'core.classes.protocols.factory', 'core.classes.protocols.unreal6', - 'core.classes.protocols.inspircd', - 'core.classes.protocol' + 'core.classes.protocols.inspircd' ] @@ -36,7 +36,7 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") - # 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.Protocol = uplink.Loader.PFactory.get() 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") @@ -106,7 +106,7 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None: 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 + uplink.Protocol = uplink.Loader.PFactory.get() # Reload Service modules for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): diff --git a/core/irc.py b/core/irc.py index 19676f1..689e8e9 100644 --- a/core/irc.py +++ b/core/irc.py @@ -7,12 +7,12 @@ from ssl import SSLSocket from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any, Optional, Union from core.classes import rehash -from core.loader import Loader -from core.classes.protocol import Protocol +from core.classes.protocols.interface import IProtocol from core.utils import tr if TYPE_CHECKING: from core.definition import MSasl + from core.loader import Loader class Irc: _instance = None @@ -24,7 +24,7 @@ class Irc: return cls._instance - def __init__(self, loader: Loader) -> 'Irc': + def __init__(self, loader: 'Loader'): # Loader class self.Loader = loader @@ -135,7 +135,7 @@ class Irc: ############################################## # CONNEXION IRC # ############################################## - def init_irc(self, ircInstance: 'Irc') -> None: + def init_irc(self) -> None: """Create a socket and connect to irc server Args: @@ -143,8 +143,8 @@ class Irc: """ try: self.init_service_user() - self.Utils.create_socket(ircInstance) - self.__connect_to_irc(ircInstance) + self.Utils.create_socket(self) + self.__connect_to_irc() except AssertionError as ae: self.Logs.critical(f'Assertion error: {ae}') @@ -161,23 +161,20 @@ class Irc: )) return None - def __connect_to_irc(self, ircInstance: 'Irc') -> None: + def __connect_to_irc(self) -> None: try: self.init_service_user() - 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: 'IProtocol' = self.Loader.PFactory.get() 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) + time.sleep(3) + # self.ModuleUtils.db_load_all_existing_modules(self) while self.signal: try: if self.Config.DEFENDER_RESTART == 1: - rehash.restart_service(self.ircObject) + rehash.restart_service(self) # 4072 max what the socket can grab buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) @@ -202,9 +199,11 @@ class Irc: self.Logs.error(f"SSLEOFError __connect_to_irc: {soe} - {data}") except ssl.SSLError as se: self.Logs.error(f"SSLError __connect_to_irc: {se} - {data}") - sys.exit(1) + sys.exit(-1) except OSError as oe: - self.Logs.error(f"SSLError __connect_to_irc: {oe} - {data}") + self.Logs.error(f"SSLError __connect_to_irc: {oe} {oe.errno}") + if oe.errno == 10053: + sys.exit(-1) except (socket.error, ConnectionResetError): self.Logs.debug("Connexion reset") @@ -790,8 +789,9 @@ class Irc: if admin_obj: self.Protocol.send_priv_msg(nick_from=dnickname, - msg=f"[ {GREEN}{str(current_command).upper()}{NOGC} ] - You are already connected to {dnickname}", + msg=f"[ {GREEN}{str(current_command).upper()}{NOGC} ] - {fromuser} is already connected to {dnickname}", channel=dchanlog) + self.Protocol.send_notice(dnickname, fromuser, tr("You are already connected to %s", dnickname)) return None mes_donnees = {'user': user_to_log, 'password': self.Loader.Utils.hash_password(password)} @@ -1179,7 +1179,7 @@ class Irc: self.Config.DEFENDER_INIT = 1 # set init to 1 saying that the service will be re initiated case 'rehash': - rehash.rehash_service(self.ircObject, fromuser) + rehash.rehash_service(self, fromuser) return None case 'show_modules': diff --git a/core/loader.py b/core/loader.py index 33f0c3b..b92f2d6 100644 --- a/core/loader.py +++ b/core/loader.py @@ -8,6 +8,8 @@ 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 +import core.irc as irc +import core.classes.protocols.factory as factory class Loader: @@ -63,4 +65,8 @@ class Loader: self.Sasl: sasl.Sasl = sasl.Sasl(self) + self.Irc: irc.Irc = irc.Irc(self) + + self.PFactory: factory.ProtocolFactorty = factory.ProtocolFactorty(self.Irc) + self.Logs.debug(self.Utils.tr("Loader %s success", __name__)) diff --git a/core/logs.py b/core/logs.py index 19f2e62..fbf02a7 100644 --- a/core/logs.py +++ b/core/logs.py @@ -15,7 +15,7 @@ class ServiceLogging: self.SERVER_PREFIX = None self.LOGGING_CONSOLE = True - self.LOG_FILTERS: list[str] = ['PING', f":{self.SERVER_PREFIX}auth", "['PASS'"] + self.LOG_FILTERS: list[str] = ["PING", f":{self.SERVER_PREFIX}auth", "['PASS'"] self.file_handler = None self.stdout_handler = None diff --git a/core/utils.py b/core/utils.py index bf4f350..bbfca6f 100644 --- a/core/utils.py +++ b/core/utils.py @@ -36,7 +36,7 @@ def tr(message: str, *args) -> str: is_args_available = True if args else False g = global_settings try: - # Access to user object ==> global_instance.get_user_option + # Access to admin object client_language = g.current_admin.language if g.current_admin else g.global_lang if count_args != count_placeholder: @@ -56,7 +56,7 @@ def tr(message: str, *args) -> str: return message % args if is_args_available else message except KeyError as ke: - g.global_logger.error(f"Key Error: {ke}") + g.global_logger.error(f"KeyError: {ke}") return message % args if is_args_available else message except Exception as err: @@ -143,6 +143,8 @@ def create_socket(uplink: 'Irc') -> None: uplink.Logs.critical(f"[OS Error]: {oe}") if 'connection refused' in str(oe).lower(): sys.exit(oe) + if oe.errno == 10053: + sys.exit(oe) except AttributeError as ae: uplink.Logs.critical(f"AttributeError: {ae}") @@ -241,4 +243,4 @@ def hide_sensitive_data(srvmsg: list[str]) -> list[str]: return srv_msg except ValueError: - return srvmsg \ No newline at end of file + return srvmsg diff --git a/defender.py b/defender.py index ace6ab3..6b5e1bc 100644 --- a/defender.py +++ b/defender.py @@ -11,14 +11,17 @@ from core import installation try: - installation.Install() + # installation.Install() from core.loader import Loader - from core.irc import Irc - ircInstance = Irc(Loader()) - ircInstance.init_irc(ircInstance) + loader = Loader() + loader.Irc.init_irc() + # from core.irc import Irc + # ircInstance = Irc(Loader()) + # ircInstance.init_irc(ircInstance) except AssertionError as ae: print(f'Assertion Error -> {ae}') except KeyboardInterrupt as k: - ircInstance.Base.execute_periodic_action() \ No newline at end of file + # ircInstance.Base.execute_periodic_action() + ... \ No newline at end of file From fd9643eddc65a96f17217f22d82228ed9e09c12a Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:37:41 +0200 Subject: [PATCH 3/6] Add command handler system. Starting adapt the modules to fit other protocls. --- .vscode/settings.json | 5 + core/classes/protocols/command_handler.py | 48 ++ core/classes/protocols/inspircd.py | 881 ++++++++++++++++++++-- core/classes/protocols/interface.py | 103 ++- core/classes/protocols/unreal6.py | 265 ++++++- core/classes/rehash.py | 35 +- core/classes/reputation.py | 10 +- core/classes/settings.py | 11 +- core/classes/user.py | 2 +- core/definition.py | 13 +- core/irc.py | 127 +--- defender.py | 2 +- mods/clone/mod_clone.py | 15 +- mods/clone/utils.py | 18 +- mods/command/utils.py | 9 +- mods/defender/mod_defender.py | 4 +- mods/defender/utils.py | 31 +- version.json | 2 +- 18 files changed, 1319 insertions(+), 262 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 core/classes/protocols/command_handler.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d49077b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.fontFamily": "Fira Code", + "editor.fontSize": 14, + "editor.cursorStyle": "block" +} \ No newline at end of file diff --git a/core/classes/protocols/command_handler.py b/core/classes/protocols/command_handler.py new file mode 100644 index 0000000..b06b9e6 --- /dev/null +++ b/core/classes/protocols/command_handler.py @@ -0,0 +1,48 @@ +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from core.definition import MIrcdCommand + from core.loader import Loader + +class CommandHandler: + + DB_IRCDCOMMS: list['MIrcdCommand'] = [] + DB_SUBSCRIBE: list = [] + + def __init__(self, loader: 'Loader'): + self.__Logs = loader.Logs + + def register(self, ircd_command_model: 'MIrcdCommand') -> None: + """Register a new command in the Handler + + Args: + ircd_command_model (MIrcdCommand): The IRCD Command Object + """ + ircd_command = self.get_registred_ircd_command(ircd_command_model.command_name) + if ircd_command is None: + self.__Logs.debug(f'[IRCD COMMAND HANDLER] New IRCD command ({ircd_command_model.command_name}) added to the handler.') + self.DB_IRCDCOMMS.append(ircd_command_model) + return None + else: + self.__Logs.debug(f'[IRCD COMMAND HANDLER] This IRCD command ({ircd_command.command_name}) already exist in the handler.') + + def get_registred_ircd_command(self, command_name: str) -> Optional['MIrcdCommand']: + """Get the registred IRCD command model + + Returns: + MIrcdCommand: The IRCD Command object + """ + com = command_name.upper() + for ircd_com in self.DB_IRCDCOMMS: + if com == ircd_com.command_name.upper(): + return ircd_com + + return None + + def get_ircd_commands(self) -> list['MIrcdCommand']: + """Get the list of IRCD Commands + + Returns: + list[MIrcdCommand]: a list of all registred commands + """ + return self.DB_IRCDCOMMS.copy() diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index d41a9db..531227d 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -1,33 +1,42 @@ -from re import match, findall -from datetime import datetime import sys -from typing import TYPE_CHECKING, Optional +from base64 import b64decode +from re import match, findall, search +from datetime import datetime +from typing import TYPE_CHECKING, Any, Optional from ssl import SSLEOFError, SSLError +from core.classes.protocols.command_handler import CommandHandler +from core.classes.protocols.interface import IProtocol +from core.utils import tr if TYPE_CHECKING: from core.irc import Irc + from core.definition import MSasl, MClient -class Inspircd: +class Inspircd(IProtocol): def __init__(self, ircInstance: 'Irc'): self.name = 'InspIRCd-4' + self.protocol_version = 1206 self.__Irc = ircInstance self.__Config = ircInstance.Config self.__Base = ircInstance.Base self.__Utils = ircInstance.Loader.Utils + self.__Settings = ircInstance.Settings self.__Logs = ircInstance.Loader.Logs - self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', - 'EOS', 'PRIVMSG', 'MODE', 'UMODE2', - 'VERSION', 'REPUTATION', 'SVS2MODE', - 'SLOG', 'NICK', 'PART', 'PONG', 'SASL', 'PING', - 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO', - '006', '007', '018'} + self.known_protocol: set[str] = {'UID', 'ERROR', 'PRIVMSG', + 'SINFO', 'FJOIN', 'PING', 'PONG', + 'SASL', 'PART', 'CAPAB', 'ENDBURST', + 'METADATA', 'NICK', + 'MODE', 'QUIT', 'SQUIT', + 'VERSION'} - self.__Logs.info(f"** Loading protocol [{__name__}]") + self.Handler = CommandHandler(ircInstance.Loader) - def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + self.__Logs.info(f"[PROTOCOL] Protocol [{__name__}] loaded!") + + def get_ircd_protocol_poisition(self, cmd: list[str], log: bool = False) -> tuple[int, Optional[str]]: """Get the position of known commands Args: @@ -39,11 +48,28 @@ class Inspircd: for index, token in enumerate(cmd): if token.upper() in self.known_protocol: return index, token.upper() - - self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") + + if log: + self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") return (-1, None) + def register_command(self): + m = self.__Irc.Loader.Definition.MIrcdCommand + self.Handler.register(m('PING', self.on_server_ping)) + self.Handler.register(m('NICK', self.on_nick)) + self.Handler.register(m('SASL', self.on_sasl)) + self.Handler.register(m('SINFO', self.on_server)) + self.Handler.register(m('UID', self.on_uid)) + self.Handler.register(m('QUIT', self.on_quit)) + self.Handler.register(m('FJOIN', self.on_sjoin)) + self.Handler.register(m('PART', self.on_part)) + self.Handler.register(m('PRIVMSG', self.on_privmsg)) + self.Handler.register(m('ERROR', self.on_error)) + self.Handler.register(m('CAPAB', self.on_protoctl)) + self.Handler.register(m('ENDBURST', self.on_endburst)) + self.Handler.register(m('METADATA', self.on_metedata)) + def send2socket(self, message: str, print_log: bool = True) -> None: """Envoit les commandes à envoyer au serveur. @@ -149,17 +175,25 @@ class Inspircd: link = self.__Config.SERVEUR_LINK server_id = self.__Config.SERVEUR_ID service_id = self.__Config.SERVICE_ID + server_hostname = self.__Config.SERVEUR_HOSTNAME version = self.__Config.CURRENT_VERSION unixtime = self.__Utils.get_unixtime() + self.__Settings.MAIN_SERVER_HOSTNAME = server_hostname - - self.send2socket(f"CAPAB START 1206") + self.send2socket(f"CAPAB START {self.protocol_version}") + self.send2socket(f"CAPAB MODULES :services") + self.send2socket(f"CAPAB MODSUPPORT :") self.send2socket(f"CAPAB CAPABILITIES :NICKMAX=30 CHANMAX=64 MAXMODES=20 IDENTMAX=10 MAXQUIT=255 MAXTOPIC=307 MAXKICK=255 MAXREAL=128 MAXAWAY=200 MAXHOST=64 MAXLINE=512 CASEMAPPING=ascii GLOBOPS=0") self.send2socket(f"CAPAB END") self.send2socket(f"SERVER {link} {password} {server_id} :{info}") self.send2socket(f"BURST {unixtime}") + self.send2socket(f":{server_id} SINFO version :{service_name}-{version.split('.')[0]}. {server_hostname} :") + self.send2socket(f":{server_id} SINFO fullversion :{service_name}-{version}. {host} :") + self.send2socket(f":{server_id} SINFO rawversion :{service_name}-{version}") + self.send_uid(nickname, username, host, service_id, smodes, host, "127.0.0.1", realname) self.send2socket(f":{server_id} ENDBURST") + # self.send_sjoin(chan) self.__Logs.debug(f'>> {__name__} Link information sent to the server') @@ -175,6 +209,58 @@ class Inspircd: self.send2socket(f":{self.__Config.SERVICE_NICKNAME} NICK {newnickname}") return None + def send_set_mode(self, modes: str, *, nickname: Optional[str] = None, channel_name: Optional[str] = None, params: Optional[str] = None) -> None: + """Set a mode to channel or to a nickname or for a user in a channel + + Args: + modes (str): The selected mode + nickname (Optional[str]): The nickname + channel_name (Optional[str]): The channel name + """ + service_id = self.__Config.SERVICE_ID + params = '' if params is None else params + + if modes[0] not in ['+', '-']: + self.__Logs.error(f"[MODE ERROR] The mode you have provided is missing the sign: {modes}") + return None + + if nickname and channel_name: + # :98KAAAAAB MODE #services +o defenderdev + if not self.__Irc.Channel.is_valid_channel(channel_name): + self.__Logs.error(f"[MODE ERROR] The channel is not valid: {channel_name}") + return None + + if not all(mode in self.__Settings.PROTOCTL_PREFIX_MODES_SIGNES for mode in list(modes.replace('+','').replace('-',''))): + self.__Logs.debug(f'[USERMODE UNVAILABLE] This mode {modes} is not available!') + return None + + self.send2socket(f":{service_id} MODE {channel_name} {modes} {nickname}") + return None + + if nickname and channel_name is None: + # :98KAAAAAB MODE nickname +o + if not all(mode in self.__Settings.PROTOCTL_USER_MODES for mode in list(modes.replace('+','').replace('-',''))): + self.__Logs.debug(f'[USERMODE UNVAILABLE] This mode {modes} is not available!') + return None + + self.send2socket(f":{service_id} MODE {nickname} {modes}") + return None + + if nickname is None and channel_name: + # :98KAAAAAB MODE #channel +o + if not all(mode in self.__Settings.PROTOCTL_CHANNEL_MODES for mode in list(modes.replace('+','').replace('-',''))): + self.__Logs.debug(f'[USERMODE UNVAILABLE] This mode {modes} is not available!') + return None + + if not self.__Irc.Channel.is_valid_channel(channel_name): + self.__Logs.error(f"[MODE ERROR] The channel is not valid: {channel_name}") + return None + + self.send2socket(f":{service_id} MODE {channel_name} {modes} {params}") + return None + + return None + def send_squit(self, server_id: str, server_link: str, reason: str) -> None: if not reason: @@ -197,16 +283,25 @@ class Inspircd: return None def send_sjoin(self, channel: str) -> None: + """Service join a channel + + Args: + channel (str): The channel name. + """ + server_id = self.__Config.SERVEUR_ID + service_nickname = self.__Config.SERVICE_NICKNAME + service_modes = self.__Config.SERVICE_UMODES + service_id = self.__Config.SERVICE_ID if not self.__Irc.Channel.is_valid_channel(channel): self.__Logs.error(f"The channel [{channel}] is not valid") return None - # self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Utils.get_unixtime()} {channel} + :{self.__Config.SERVICE_ID}") - self.send2socket(f":{self.__Config.SERVICE_ID} FJOIN {channel} 68") + self.send2socket(f":{server_id} FJOIN {channel} {self.__Utils.get_unixtime()} :o, {service_id}") + self.send_set_mode(service_modes, nickname=service_nickname, channel_name=channel) # Add defender to the channel uids list - self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[self.__Config.SERVICE_ID])) + self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[service_id])) return None def send_quit(self, uid: str, reason: str, print_log: True) -> None: @@ -217,7 +312,7 @@ class Inspircd: reason (str): The reason for the quit """ user_obj = self.__Irc.User.get_user(uidornickname=uid) - reputationObj = self.__Irc.Reputation.get_Reputation(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) @@ -233,7 +328,7 @@ class Inspircd: def send_uid(self, nickname:str, username: str, hostname: str, uid:str, umodes: str, vhost: str, remote_ip: str, realname: str, print_log: bool = True) -> None: """Send UID to the server - + [:] UID []+ : Args: nickname (str): Nickname of the client username (str): Username of the client @@ -249,19 +344,27 @@ class Inspircd: # {clone.nickname} 1 {self.__Utils.get_unixtime()} {clone.username} {clone.hostname} {clone.uid} * {clone.umodes} {clone.vhost} * {self.Base.encode_ip(clone.remote_ip)} :{clone.realname} try: unixtime = self.__Utils.get_unixtime() - encoded_ip = self.__Base.encode_ip(remote_ip) + # encoded_ip = self.__Base.encode_ip(remote_ip) + new_umodes = [] + for mode in list(umodes.replace('+', '').replace('-', '')): + if mode in self.__Settings.PROTOCTL_USER_MODES: + new_umodes.append(mode) + + final_umodes = '+' + ''.join(new_umodes) # Create the user self.__Irc.User.insert( self.__Irc.Loader.Definition.MUser( uid=uid, nickname=nickname, username=username, - realname=realname,hostname=hostname, umodes=umodes, + realname=realname,hostname=hostname, umodes=final_umodes, vhost=vhost, remote_ip=remote_ip ) ) - uid_msg = f":{self.__Config.SERVEUR_ID} UID {nickname} 1 {unixtime} {username} {hostname} {uid} * {umodes} {vhost} * {encoded_ip} :{realname}" - + # [:] UID []+ : + # :98K UID 98KAAAAAB 1756932359 defenderdev defenderdev.deb.biz.st defenderdev.deb.biz.st Dev-PyDefender 127.0.0.1 1756932359 + :Dev Python Security + # [':97K', 'UID', '97KAAAAAA', '1756926679', 'adator', '172.18.128.1', 'attila.example.org', '...', '...', '172.18.128.1', '1756926678', '+o', ':...'] + uid_msg = f":{self.__Config.SERVEUR_ID} UID {uid} {unixtime} {nickname} {hostname} {vhost} {username} {username} {remote_ip} {unixtime} {final_umodes} :{realname}" self.send2socket(uid_msg, print_log=print_log) return None @@ -289,7 +392,7 @@ class Inspircd: self.__Logs.error(f"The channel [{channel}] is not valid") return None - self.send2socket(f":{userObj.uid} JOIN {channel} {passwordChannel}", print_log=print_log) + self.send2socket(f":{userObj.uid} FJOIN {channel} {self.__Utils.get_unixtime()} :,{userObj.uid} {passwordChannel}", print_log=print_log) # Add defender to the channel uids list self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[userObj.uid])) @@ -326,6 +429,15 @@ class Inspircd: return None + def send_raw(self, raw_command: str) -> None: + + self.send2socket(f":{self.__Config.SERVEUR_ID} {raw_command}") + return None + + # ------------------------------------------------------------------------ + # RECIEVED IRC MESSAGES + # ------------------------------------------------------------------------ + def on_umode2(self, serverMsg: list[str]) -> None: """Handle umode2 coming from a server @@ -358,14 +470,13 @@ class Inspircd: def on_quit(self, serverMsg: list[str]) -> None: """Handle quit coming from a server - + >> [':97KAAAAAZ', 'QUIT', ':Quit:', '....'] Args: serverMsg (list[str]): Original server message """ try: - # ['@unrealircd.org/userhost=...@192.168.1.10;unrealircd.org/userip=...@192.168.1.10;msgid=CssUrV08BzekYuq7BfvPHn;time=2024-11-02T15:03:33.182Z', ':001JKNY0N', 'QUIT', ':Quit:', '....'] - uid_who_quit = str(serverMsg[1]).lstrip(':') + uid_who_quit = str(serverMsg[0]).lstrip(':') self.__Irc.Channel.delete_user_from_all_channel(uid_who_quit) self.__Irc.User.delete(uid_who_quit) @@ -401,15 +512,49 @@ class Inspircd: return None def on_protoctl(self, serverMsg: list[str]) -> None: - """Handle protoctl coming from a server + """Handle CAPAB coming from a server Args: serverMsg (list[str]): Original server message """ - if len(serverMsg) > 5: - if '=' in serverMsg[5]: - serveur_hosting_id = str(serverMsg[5]).split('=') - self.__Config.HSID = serveur_hosting_id[1] + # ['CAPAB', 'CHANMODES', ':list:ban=b', 'param-set:limit=l', 'param:key=k', 'prefix:10000:voice=+v', 'prefix:30000:op=@o', 'prefix:50000:founder=~q', + # 'simple:c_registered=r', 'simple:inviteonly=i', 'simple:moderated=m', 'simple:noextmsg=n', 'simple:private=p', + # 'simple:secret=s', 'simple:sslonly=z', 'simple:topiclock=t'] + + scopy = serverMsg.copy() + + # Get Chan modes. + if scopy[1] == 'CHANMODES': + sign_mode = {} + mode_sign = {} + channel_modes = [] + for prefix in scopy: + if prefix.startswith('prefix:'): + sign = prefix.split('=')[1][0] if len(prefix.split('=')) > 1 else None + mode = prefix.split('=')[1][1] if len(prefix.split('=')) > 1 else None + sign_mode[sign] = mode + mode_sign[mode] = sign + + if prefix.startswith('simple:') or prefix.startswith('param-set:') or prefix.startswith('param:'): + cmode = prefix.split('=')[1] if len(prefix.split('=')) > 1 else None + channel_modes.append(cmode) + + + self.__Settings.PROTOCTL_PREFIX_SIGNES_MODES = sign_mode + self.__Settings.PROTOCTL_PREFIX_MODES_SIGNES = mode_sign + self.__Settings.PROTOCTL_CHANNEL_MODES = list(set(channel_modes)) + + # ['CAPAB', 'USERMODES', ':param-set:snomask=s', 'simple:bot=B', 'simple:invisible=i', 'simple:oper=o', 'simple:servprotect=k', + # 'simple:sslqueries=z', 'simple:u_registered=r', 'simple:wallops=w'] + # Get user modes + if scopy[1] == 'USERMODES': + user_modes = [] + for prefix in scopy: + if prefix.startswith('param-set:') or prefix.startswith('simple:'): + umode = prefix.split('=')[1] if len(prefix.split('=')) > 1 else None + user_modes.append(umode) + + self.__Settings.PROTOCTL_USER_MODES = list(set(user_modes)) return None @@ -421,11 +566,15 @@ class Inspircd: serverMsg (list[str]): Original server message """ try: - # ['@unrealircd.org/geoip=FR;unrealircd.org/', ':001OOU2H3', 'NICK', 'WebIrc', '1703795844'] + # [':97KAAAAAF', 'NICK', 'test', '1757370509'] # Changement de nickname - uid = str(serverMsg[1]).lstrip(':') - newnickname = serverMsg[3] + scopy = serverMsg.copy() + if scopy[0].startswith('@'): + scopy.pop(0) + + uid = str(scopy[0]).replace(':','') + newnickname = scopy[2] self.__Irc.User.update_nickname(uid, newnickname) self.__Irc.Client.update_nickname(uid, newnickname) self.__Irc.Admin.update_nickname(uid, newnickname) @@ -444,33 +593,17 @@ class Inspircd: serverMsg (list[str]): Original server message """ try: - # ['@msgid=5sTwGdj349D82L96p749SY;time=2024-08-15T09:50:23.528Z', ':001', 'SJOIN', '1721564574', '#welcome', ':001JD94QH'] - # ['@msgid=bvceb6HthbLJapgGLXn1b0;time=2024-08-15T09:50:11.464Z', ':001', 'SJOIN', '1721564574', '#welcome', '+lnrt', '13', ':001CIVLQF', '+11ZAAAAAB', '001QGR10C', '*@0014UE10B', '001NL1O07', '001SWZR05', '001HB8G04', '@00BAAAAAJ', '0019M7101'] - # ['@msgid=SKUeuVzOrTShRDduq8VerX;time=2024-08-23T19:37:04.266Z', ':001', 'SJOIN', '1723993047', '#welcome', '+lnrt', '13', - # ':001T6VU3F', '001JGWB2K', '@11ZAAAAAB', - # '001F16WGR', '001X9YMGQ', '*+001DYPFGP', '@00BAAAAAJ', '001AAGOG9', '001FMFVG8', '001DAEEG7', - # '&~G:unknown-users', '"~G:websocket-users', '"~G:known-users', '"~G:webirc-users'] - serverMsg.pop(0) - channel = str(serverMsg[3]).lower() - len_cmd = len(serverMsg) + # [':97K', 'FJOIN', '#services', '1757156589', '+nt', ':,97KAAAAA2:22', 'o,97KAAAAAA:2'] + + channel = str(serverMsg[2]).lower() list_users:list = [] - occurence = 0 - start_boucle = 0 - # Trouver le premier user - for i in range(len_cmd): - s: list = findall(fr':', serverMsg[i]) - if s: - occurence += 1 - if occurence == 2: - start_boucle = i + # Find uid's + for uid in serverMsg: + matches = findall(r',([0-9A-Z]+):', uid) + list_users.extend(matches) - # Boucle qui va ajouter l'ensemble des users (UID) - for i in range(start_boucle, len(serverMsg)): - parsed_UID = str(serverMsg[i]) - clean_uid = self.__Utils.clean_uid(parsed_UID) - if not clean_uid is None and len(clean_uid) == 9: - list_users.append(parsed_UID) + list_users = list(set(list_users)) if list_users: self.__Irc.Channel.insert( @@ -486,6 +619,80 @@ class Inspircd: except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") + def on_endburst(self, serverMsg: list[str]) -> None: + """Handle EOS coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + try: + # [':97K', 'ENDBURST'] + scopy = serverMsg.copy() + hsid = str(scopy[0]).replace(':','') + if hsid == self.__Config.HSID: + if self.__Config.DEFENDER_INIT == 1: + current_version = self.__Config.CURRENT_VERSION + latest_version = self.__Config.LATEST_VERSION + if self.__Base.check_for_new_version(False): + version = f'{current_version} >>> {latest_version}' + else: + version = f'{current_version}' + + print(f"################### DEFENDER ###################") + print(f"# SERVICE CONNECTE ") + print(f"# SERVEUR : {self.__Config.SERVEUR_IP} ") + print(f"# PORT : {self.__Config.SERVEUR_PORT} ") + print(f"# SSL : {self.__Config.SERVEUR_SSL} ") + print(f"# SSL VER : {self.__Config.SSL_VERSION} ") + print(f"# NICKNAME : {self.__Config.SERVICE_NICKNAME} ") + print(f"# CHANNEL : {self.__Config.SERVICE_CHANLOG} ") + print(f"# VERSION : {version} ") + print(f"################################################") + + self.__Logs.info(f"################### DEFENDER ###################") + self.__Logs.info(f"# SERVICE CONNECTE ") + self.__Logs.info(f"# SERVEUR : {self.__Config.SERVEUR_IP} ") + self.__Logs.info(f"# PORT : {self.__Config.SERVEUR_PORT} ") + self.__Logs.info(f"# SSL : {self.__Config.SERVEUR_SSL} ") + self.__Logs.info(f"# SSL VER : {self.__Config.SSL_VERSION} ") + self.__Logs.info(f"# NICKNAME : {self.__Config.SERVICE_NICKNAME} ") + self.__Logs.info(f"# CHANNEL : {self.__Config.SERVICE_CHANLOG} ") + self.__Logs.info(f"# VERSION : {version} ") + self.__Logs.info(f"################################################") + + self.send_sjoin(self.__Config.SERVICE_CHANLOG) + + if self.__Base.check_for_new_version(False): + self.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f" New Version available {version}", + channel=self.__Config.SERVICE_CHANLOG + ) + + # Initialisation terminé aprés le premier PING + self.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=tr("[ %sINFORMATION%s ] >> %s is ready!", self.__Config.COLORS.green, self.__Config.COLORS.nogc, self.__Config.SERVICE_NICKNAME), + channel=self.__Config.SERVICE_CHANLOG + ) + self.__Config.DEFENDER_INIT = 0 + + # Send EOF to other modules + for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): + module.class_instance.cmd(scopy) + + # Join saved channels & load existing modules + self.__Irc.join_saved_channels() + self.__Irc.ModuleUtils.db_load_all_existing_modules(self.__Irc) + + return None + except IndexError as ie: + self.__Logs.error(f"{__name__} - Key Error: {ie}") + except KeyError as ke: + self.__Logs.error(f"{__name__} - Key Error: {ke}") + except Exception as err: + self.__Logs.error(f"{__name__} - General Error: {err}") + def on_part(self, serverMsg: list[str]) -> None: """Handle part coming from a server @@ -493,11 +700,11 @@ class Inspircd: serverMsg (list[str]): Original server message """ try: - # ['@unrealircd.org/geoip=FR;unrealircd.org/userhost=50d6492c@80.214.73.44;unrealircd.org/userip=50d6492c@80.214.73.44;msgid=YSIPB9q4PcRu0EVfC9ci7y-/mZT0+Gj5FLiDSZshH5NCw;time=2024-08-15T15:35:53.772Z', - # ':001EPFBRD', 'PART', '#welcome', ':WEB', 'IRC', 'Paris'] + # [':97KAAAAA2', 'PART', '#v', ':"Closing', 'Window"'] - uid = str(serverMsg[1]).lstrip(':') - channel = str(serverMsg[3]).lower() + uid = str(serverMsg[0]).lstrip(':') + channel = str(serverMsg[2]).lower() + reason = str(' '.join(serverMsg[3:])) self.__Irc.Channel.delete_user_from_channel(channel, uid) return None @@ -515,6 +722,9 @@ class Inspircd: serverMsg (list[str]): Original server message """ try: + RED = self.__Config.COLORS.red + GREEN = self.__Config.COLORS.green + NOGC = self.__Config.COLORS.nogc isWebirc = True if 'webirc' in serverMsg[0] else False isWebsocket = True if 'websocket' in serverMsg[0] else False @@ -526,7 +736,8 @@ class Inspircd: vhost = str(serverMsg[6]) if not 'S' in umodes: - remote_ip = self.__Base.decode_ip(str(serverMsg[9])) + # remote_ip = self.__Base.decode_ip(str(serverMsg[9])) + remote_ip = str(serverMsg[9]) else: remote_ip = '127.0.0.1' @@ -561,12 +772,127 @@ class Inspircd: connexion_datetime=datetime.now() ) ) + + for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): + module.class_instance.cmd(serverMsg) + + # SASL authentication + dnickname = self.__Config.SERVICE_NICKNAME + dchanlog = self.__Config.SERVICE_CHANLOG + # uid = serverMsg[8] + # nickname = serverMsg[3] + sasl_obj = self.__Irc.Sasl.get_sasl_obj(uid) + if sasl_obj: + if sasl_obj.auth_success: + self.__Irc.insert_db_admin(sasl_obj.client_uid, sasl_obj.username, sasl_obj.level, sasl_obj.language) + self.send_priv_msg(nick_from=dnickname, + msg=tr("[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, nickname, sasl_obj.username, dnickname), + channel=dchanlog) + self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Successfuly connected to %s", dnickname)) + else: + self.send_priv_msg(nick_from=dnickname, + msg=tr("[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s", RED, NOGC, nickname, sasl_obj.username), + channel=dchanlog) + self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Wrong password!")) + + # Delete sasl object! + self.__Irc.Sasl.delete_sasl_client(uid) + return None + return None except IndexError as ie: self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}", exc_info=True) + def on_privmsg(self, serverMsg: list[str]) -> None: + """Handle PRIVMSG message coming from the server + + Args: + serverMsg (list[str]): Original server message + """ + try: + srv_msg = serverMsg.copy() + cmd = serverMsg.copy() + # Supprimer la premiere valeur si MTAGS activé + if cmd[0].startswith('@'): + cmd.pop(0) + + 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 . + + if hcmds: # Commande qui commencent par le point + liste_des_commandes = list(hcmds.groups()) + convert_to_string = ' '.join(liste_des_commandes) + arg = convert_to_string.split() + arg.remove(f':{self.__Config.SERVICE_PREFIX}') + 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, + 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) + + fromchannel = str(cmd[2]).lower() if self.__Irc.Channel.is_valid_channel(cmd[2]) else None + self.__Irc.hcmds(user_trigger, fromchannel, arg, cmd) + + if cmd[2] == self.__Config.SERVICE_ID: + pattern = fr'^:.*?:(.*)$' + hcmds = search(pattern, ' '.join(cmd)) + + if hcmds: # par /msg defender [commande] + liste_des_commandes = list(hcmds.groups()) + convert_to_string = ' '.join(liste_des_commandes) + arg = convert_to_string.split() + + # Réponse a un CTCP VERSION + if arg[0] == '\x01VERSION\x01': + self.on_version(srv_msg) + return None + + # Réponse a un TIME + if arg[0] == '\x01TIME\x01': + self.on_time(srv_msg) + return None + + # Réponse a un PING + if arg[0] == '\x01PING': + self.on_ping(srv_msg) + return None + + 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 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) + + fromchannel = None + if len(arg) >= 2: + fromchannel = str(arg[1]).lower() if self.__Irc.Channel.is_valid_channel(arg[1]) else None + + self.__Irc.hcmds(user_trigger, fromchannel, arg, cmd) + return None + + except KeyError as ke: + self.__Logs.error(f"Key Error: {ke}") + except AttributeError as ae: + self.__Logs.error(f"Attribute Error: {ae}") + except Exception as err: + self.__Logs.error(f"General Error: {err} - {srv_msg}") + def on_server_ping(self, serverMsg: list[str]) -> None: """Send a PONG message to the server @@ -574,12 +900,12 @@ class Inspircd: serverMsg (list[str]): List of str coming from the server """ try: - # InspIRCd 3: + # InspIRCd 4: # <- :3IN PING 808 # -> :808 PONG 3IN hsid = str(serverMsg[0]).replace(':','') - self.send2socket(f":{self.__Config.SERVEUR_ID} PONG {hsid}", print_log=True) + self.send2socket(f":{self.__Config.SERVEUR_ID} PONG {hsid}", print_log=False) return None except Exception as err: @@ -587,12 +913,20 @@ class Inspircd: def on_server(self, serverMsg: list[str]) -> None: """_summary_ - + >>> [':97K', 'SINFO', 'customversion', ':'] + >>> [':97K', 'SINFO', 'rawbranch', ':InspIRCd-4'] + >>> [':97K', 'SINFO', 'rawversion', ':InspIRCd-4.8.0'] Args: - serverMsg (list[str]): _description_ + serverMsg (list[str]): The server message """ try: - ... + param = str(serverMsg[2]) + self.__Config.HSID = self.__Settings.MAIN_SERVER_ID = str(serverMsg[0]).replace(':', '') + if param == 'rawversion': + self.__Logs.debug(f">> Server Version: {serverMsg[3].replace(':', '')}") + elif param == 'rawbranch': + self.__Logs.debug(f">> Branch Version: {serverMsg[3].replace(':', '')}") + except Exception as err: self.__Logs.error(f'General Error: {err}') @@ -702,3 +1036,408 @@ class Inspircd: except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") + + def on_sasl(self, serverMsg: list[str]) -> Optional['MSasl']: + """Handle SASL coming from a server + + Args: + serverMsg (list[str]): Original server message + psasl (Sasl): The SASL process object + """ + try: + # [':97K', 'ENCAP', '98K', 'SASL', '97KAAAAAF', '*', 'H', '172.18.128.1', '172.18.128.1', 'P'] + # [':97K', 'ENCAP', '98K', 'SASL', '97KAAAAAF', '*', 'S', 'PLAIN'] + # [':97K', 'ENCAP', '98K', 'SASL', '97KAAAAAP', 'irc.inspircd.local', 'C', 'YWRzefezfzefzefzefzefzefzefzezak='] + # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '0014ZZH1F', 'S', 'EXTERNAL', 'zzzzzzzkey'] + # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '00157Z26U', 'C', 'sasakey=='] + # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '00157Z26U', 'D', 'A'] + psasl = self.__Irc.Sasl + sasl_enabled = True # Should be False + for smod in self.__Settings.SMOD_MODULES: + if smod.name == 'sasl': + sasl_enabled = True + break + + if not sasl_enabled: + return None + + sCopy = serverMsg.copy() + client_uid = sCopy[4] if len(sCopy) >= 6 else None + sasl_obj = None + sasl_message_type = sCopy[6] if len(sCopy) >= 6 else None + psasl.insert_sasl_client(self.__Irc.Loader.Definition.MSasl(client_uid=client_uid)) + sasl_obj = psasl.get_sasl_obj(client_uid) + + if sasl_obj is None: + return None + + match sasl_message_type: + case 'H': + sasl_obj.remote_ip = str(sCopy[8]) + sasl_obj.message_type = sasl_message_type + return sasl_obj + + case 'S': + sasl_obj.message_type = sasl_message_type + if str(sCopy[7]) in ['PLAIN', 'EXTERNAL']: + sasl_obj.mechanisme = str(sCopy[7]) + + if sasl_obj.mechanisme == "PLAIN": + self.send2socket(f":{self.__Config.SERVEUR_ID} SASL {self.__Config.SERVEUR_HOSTNAME} {sasl_obj.client_uid} C +") + elif sasl_obj.mechanisme == "EXTERNAL": + if str(sCopy[7]) == "+": + return None + + sasl_obj.fingerprint = str(sCopy[8]) + self.send2socket(f":{self.__Config.SERVEUR_ID} SASL {self.__Config.SERVEUR_HOSTNAME} {sasl_obj.client_uid} C +") + + self.on_sasl_authentication_process(sasl_obj) + return sasl_obj + + case 'C': + if sasl_obj.mechanisme == "PLAIN": + credentials = sCopy[7] + decoded_credentials = b64decode(credentials).decode() + user, username, password = decoded_credentials.split('\0') + + sasl_obj.message_type = sasl_message_type + sasl_obj.username = username + sasl_obj.password = password + + self.on_sasl_authentication_process(sasl_obj) + return sasl_obj + elif sasl_obj.mechanisme == "EXTERNAL": + sasl_obj.message_type = sasl_message_type + self.on_sasl_authentication_process(sasl_obj) + return sasl_obj + + except Exception as err: + self.__Logs.error(f'General Error: {err}', exc_info=True) + + def on_sasl_authentication_process(self, sasl_model: 'MSasl') -> bool: + s = sasl_model + server_id = self.__Config.SERVEUR_ID + main_server_hostname = self.__Settings.MAIN_SERVER_HOSTNAME + db_admin_table = self.__Config.TABLE_ADMIN + if sasl_model: + def db_get_admin_info(*, username: Optional[str] = None, password: Optional[str] = None, fingerprint: Optional[str] = None) -> Optional[dict[str, Any]]: + if fingerprint: + mes_donnees = {'fingerprint': fingerprint} + query = f"SELECT user, level, language FROM {db_admin_table} WHERE fingerprint = :fingerprint" + else: + mes_donnees = {'user': username, 'password': self.__Utils.hash_password(password)} + query = f"SELECT user, level, language FROM {db_admin_table} WHERE user = :user AND password = :password" + + result = self.__Base.db_execute_query(query, mes_donnees) + user_from_db = result.fetchone() + if user_from_db: + return {'user': user_from_db[0], 'level': user_from_db[1], 'language': user_from_db[2]} + else: + return None + + if s.message_type == 'C' and s.mechanisme == 'PLAIN': + # Connection via PLAIN + admin_info = db_get_admin_info(username=s.username, password=s.password) + if admin_info is not None: + s.auth_success = True + s.level = admin_info.get('level', 0) + s.language = admin_info.get('language', 'EN') + self.send2socket(f":{server_id} SASL {main_server_hostname} {s.client_uid} D S") + self.send2socket(f":{server_id} SASL {s.username} :SASL authentication successful") + else: + self.send2socket(f":{server_id} SASL {main_server_hostname} {s.client_uid} D F") + self.send2socket(f":{server_id} SASL {s.username} :SASL authentication failed") + + elif s.message_type == 'S' and s.mechanisme == 'EXTERNAL': + # Connection using fingerprints + admin_info = db_get_admin_info(fingerprint=s.fingerprint) + + if admin_info is not None: + s.auth_success = True + s.level = admin_info.get('level', 0) + s.username = admin_info.get('user', None) + s.language = admin_info.get('language', 'EN') + self.send2socket(f":{server_id} SASL {main_server_hostname} {s.client_uid} D S") + self.send2socket(f":{server_id} SASL {s.username} :SASL authentication successful") + else: + # "904 :SASL authentication failed" + self.send2socket(f":{server_id} SASL {main_server_hostname} {s.client_uid} D F") + self.send2socket(f":{server_id} SASL {s.username} :SASL authentication failed") + + def on_error(self, serverMsg: list[str]) -> None: + self.__Logs.debug(f"{serverMsg}") + + def on_metedata(self, serverMsg: list[str]) -> None: + """_summary_ + + Args: + serverMsg (list[str]): _description_ + """ + # [':97K', 'METADATA', '97KAAAAAA', 'ssl_cert', ':vTrSe', 'fingerprint90753683519522875', + # '/C=FR/OU=Testing/O=Test', 'Sasl/CN=localhost', '/C=FR/OU=Testing/O=Test', 'Sasl/CN=localhost'] + scopy = serverMsg.copy() + dnickname = self.__Config.SERVICE_NICKNAME + dchanlog = self.__Config.SERVICE_CHANLOG + GREEN = self.__Config.COLORS.green + NOGC = self.__Config.COLORS.nogc + if 'ssl_cert' in scopy: + fingerprint = scopy[5] + uid = scopy[2] + user_obj = self.__Irc.User.get_user(uid) + if user_obj: + user_obj.fingerprint = fingerprint + if self.__Irc.Admin.db_auth_admin_via_fingerprint(fingerprint, uid): + admin = self.__Irc.Admin.get_admin(uid) + account = admin.account if admin else '' + self.send_priv_msg(nick_from=dnickname, + msg=tr("[ %sSASL AUTO AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, user_obj.nickname, account, dnickname), + channel=dchanlog) + self.send_notice(nick_from=dnickname, nick_to=user_obj.nickname, msg=tr("Successfuly connected to %s", dnickname)) + + # ------------------------------------------------------------------------ + # COMMON IRC PARSER + # ------------------------------------------------------------------------ + + def parse_uid(self, serverMsg: list[str]) -> dict[str, str]: + """Parse UID and return dictionary. + + Args: + serverMsg (list[str]): _description_ + """ + umodes = str(serverMsg[11]) + remote_ip = serverMsg[9] if 'S' not in umodes else '127.0.0.1' + + # Extract Geoip information + pattern = r'^.*geoip=cc=(\S{2}).*$' + geoip_match = match(pattern, serverMsg[0]) + geoip = geoip_match.group(1) if geoip_match else None + + response = { + 'uid': str(serverMsg[2]), + 'nickname': str(serverMsg[4]), + 'username': str(serverMsg[7]), + 'hostname': str(serverMsg[5]), + 'umodes': umodes, + 'vhost': str(serverMsg[6]), + 'ip': remote_ip, + 'realname': ' '.join(serverMsg[12:]).lstrip(':'), + 'geoip': geoip, + 'reputation_score': 0, + 'iswebirc': True if 'webirc' in serverMsg[0] else False, + 'iswebsocket': True if 'websocket' in serverMsg[0] else False + } + return response + + def parse_quit(self, serverMsg: list[str]) -> dict[str, str]: + """Parse quit and return dictionary. + >>> [':97KAAAAAB', 'QUIT', ':Quit:', 'this', 'is', 'my', 'reason', 'to', 'quit'] + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The dictionary. + """ + scopy = serverMsg.copy() + + if scopy[0].startswith('@'): + scopy.pop(0) + + response = { + "uid": scopy[0].replace(':', ''), + "reason": " ".join(scopy[3:]) + } + return response + + def parse_nick(self, serverMsg: list[str]) -> dict[str, str]: + """Parse nick changes. + >>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740'] + + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + """ + scopy = serverMsg.copy() + if scopy[0].startswith('@'): + scopy.pop(0) + + response = { + "uid": scopy[0].replace(':', ''), + "newnickname": scopy[2], + "timestamp": scopy[3] + } + return response + + def parse_privmsg(self, serverMsg: list[str]) -> dict[str, str]: + """Parse PRIVMSG message. + >>> [':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message'] + >>> [':97KAAAAAF', 'PRIVMSG', '98KAAAAAB', ':My','Message','...'] + + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + """ + scopy = serverMsg.copy() + if scopy[0].startswith('@'): + scopy.pop(0) + + response = { + "uid_sender": scopy[0].replace(':', ''), + "uid_reciever": self.__Irc.User.get_uid(scopy[2]), + "channel": scopy[2] if self.__Irc.Channel.is_valid_channel(scopy[2]) else None, + "message": " ".join(scopy[3:]) + } + return response + + + # ------------------------------------------------------------------------ + # IRC SENDER METHODS + # ------------------------------------------------------------------------ + + def send_mode_chan(self, channel_name: str, channel_mode: str) -> None: + """_summary_ + + Args: + channel_name (str): _description_ + channel_mode (str): _description_ + """ + ... + + def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + hostname (str): _description_ + set_by (str): _description_ + expire_timestamp (int): _description_ + set_at_timestamp (int): _description_ + reason (str): _description_ + """ + ... + + def send_sajoin(self, nick_to_sajoin: str, channel_name: str) -> None: + """_summary_ + + Args: + nick_to_sajoin (str): _description_ + channel_name (str): _description_ + """ + ... + + def send_sapart(self, nick_to_sapart: str, channel_name: str) -> None: + """_summary_ + + Args: + from_nick (str): _description_ + nick_to (str): _description_ + channel_name (str): _description_ + """ + ... + + def send_svs2mode(self, nickname: str, user_mode: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + user_mode (str): _description_ + """ + ... + + def send_svsjoin(self, nick_to_part: str, channels: list[str], keys: list[str]) -> None: + """_summary_ + + Args: + nick_to_part (str): _description_ + channels (list[str]): _description_ + keys (list[str]): _description_ + """ + ... + + 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 + """ + ... + + def send_svslogout(self, client_obj: 'MClient') -> None: + """Logout a client from his account + + Args: + client_uid (str): The Client UID + """ + ... + + def send_svsmode(self, nickname: str, user_mode: str) -> None: + """_summary_ + + Args: + nickname (str): _description_ + user_mode (str): _description_ + """ + ... + + def send_svspart(self, nick_to_part: str, channels: list[str], reason: str) -> None: + """_summary_ + + Args: + nick_to_part (str): _description_ + channels (list[str]): _description_ + reason (str): _description_ + """ + ... + + def on_md(self, serverMsg: list[str]) -> None: + """Handle MD responses + [':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...'] + Args: + serverMsg (list[str]): The server reply + """ + ... + + def on_mode(self, serverMsg: list[str]) -> None: + """Handle mode coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + ... + + def on_reputation(self, serverMsg: list[str]) -> None: + """Handle REPUTATION coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + ... + + def on_smod(self, serverMsg: list[str]) -> None: + """Handle SMOD message coming from the server + + Args: + serverMsg (list[str]): Original server message + """ + ... + + def on_svs2mode(self, serverMsg: list[str]) -> None: + """Handle svs2mode coming from a server + >>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r'] + + Args: + serverMsg (list[str]): Original server message + """ + ... + + def on_eos(self, serverMsg: list[str]) -> None: + """Handle EOS coming from a server + + Args: + serverMsg (list[str]): Original server message + """ + ... diff --git a/core/classes/protocols/interface.py b/core/classes/protocols/interface.py index 17005c9..00c2d1f 100644 --- a/core/classes/protocols/interface.py +++ b/core/classes/protocols/interface.py @@ -3,21 +3,31 @@ from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from core.classes.sasl import Sasl - from core.definition import MClient, MSasl + from core.definition import MClient, MSasl, MRegister + from core.classes.protocols.command_handler import CommandHandler class IProtocol(ABC): + DB_REGISTER: list['MRegister'] = [] + Handler: Optional['CommandHandler'] = None + @abstractmethod - def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + def get_ircd_protocol_poisition(self, cmd: list[str], log: bool = False) -> tuple[int, Optional[str]]: """Get the position of known commands Args: cmd (list[str]): The server response + log (bool): If true it will log in the logger Returns: tuple[int, Optional[str]]: The position and the command. """ + @abstractmethod + def register_command(self): + """Register all commands that you need to handle + """ + @abstractmethod def send2socket(self, message: str, print_log: bool = True) -> None: """Envoit les commandes à envoyer au serveur. @@ -74,6 +84,17 @@ class IProtocol(ABC): newnickname (str): New nickname of the server """ + @abstractmethod + def send_set_mode(self, modes: str, *, nickname: Optional[str] = None, channel_name: Optional[str] = None, params: Optional[str] = None) -> None: + """Set a mode to channel or to a nickname or for a user in a channel + + Args: + modes (str): The selected mode + nickname (Optional[str]): The nickname + channel_name (Optional[str]): The channel name + params (Optional[str]): Parameters like password. + """ + @abstractmethod def send_squit(self, server_id: str, server_link: str, reason: str) -> None: """_summary_ @@ -256,15 +277,72 @@ class IProtocol(ABC): @abstractmethod def send_raw(self, raw_command: str) -> None: - """_summary_ + """Send raw message to the server Args: - raw_command (str): _description_ + raw_command (str): The raw command you want to send. """ - ##################### - # HANDLE EVENTS # - ##################### + # ------------------------------------------------------------------------ + # COMMON IRC PARSER + # ------------------------------------------------------------------------ + + @abstractmethod + def parse_uid(self, serverMsg: list[str]) -> dict[str, str]: + """Parse UID and return dictionary. + + Args: + serverMsg (list[str]): The UID IRCD message + + Returns: + dict[str, str]: The response as dictionary. + """ + + @abstractmethod + def parse_quit(self, serverMsg: list[str]) -> dict[str, str]: + """Parse quit and return dictionary. + >>> [':97KAAAAAB', 'QUIT', ':Quit:', 'this', 'is', 'my', 'reason', 'to', 'quit'] + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + """ + + @abstractmethod + def parse_nick(self, serverMsg: list[str]) -> dict[str, str]: + """Parse nick changes and return dictionary. + >>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740'] + + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + """ + + @abstractmethod + def parse_privmsg(self, serverMsg: list[str]) -> dict[str, str]: + """Parse PRIVMSG message. + >>> [':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message'] + + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + ```python + response = { + "uid": '97KAAAAAE', + "channel": '#welcome', + "message": 'This is my public message' + } + ``` + """ + + # ------------------------------------------------------------------------ + # EVENT HANDLER + # ------------------------------------------------------------------------ @abstractmethod def on_svs2mode(self, serverMsg: list[str]) -> None: @@ -438,6 +516,17 @@ class IProtocol(ABC): psasl (Sasl): The SASL process object """ + @abstractmethod + def on_sasl_authentication_process(self, sasl_model: 'MSasl') -> bool: + """Finalize sasl authentication + + Args: + sasl_model (MSasl): The sasl dataclass model + + Returns: + bool: True if success + """ + @abstractmethod def on_md(self, serverMsg: list[str]) -> None: """Handle MD responses diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index 33581bb..8e10746 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -1,9 +1,10 @@ from base64 import b64decode from re import match, findall, search from datetime import datetime -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Optional from ssl import SSLEOFError, SSLError +from core.classes.protocols.command_handler import CommandHandler from core.classes.protocols.interface import IProtocol from core.utils import tr @@ -32,13 +33,16 @@ class Unrealircd6(IProtocol): 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO', '006', '007', '018'} - self.__Logs.info(f"** Loading protocol [{__name__}]") + self.Handler = CommandHandler(ircInstance.Loader) - def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + self.__Logs.info(f"[PROTOCOL] Protocol [{__name__}] loaded!") + + def get_ircd_protocol_poisition(self, cmd: list[str], log: bool = False) -> tuple[int, Optional[str]]: """Get the position of known commands Args: cmd (list[str]): The server response + log (bool): If true it will log in the logger Returns: tuple[int, Optional[str]]: The position and the command. @@ -47,10 +51,34 @@ class Unrealircd6(IProtocol): if token.upper() in self.known_protocol: return index, token.upper() - self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") + if log: + self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") return (-1, None) + def register_command(self) -> None: + m = self.__Irc.Loader.Definition.MIrcdCommand + self.Handler.register(m(command_name="PING", func=self.on_server_ping)) + self.Handler.register(m(command_name="UID", func=self.on_uid)) + self.Handler.register(m(command_name="QUIT", func=self.on_quit)) + self.Handler.register(m(command_name="SERVER", func=self.on_server)) + self.Handler.register(m(command_name="SJOIN", func=self.on_sjoin)) + self.Handler.register(m(command_name="EOS", func=self.on_eos)) + self.Handler.register(m(command_name="PROTOCTL", func=self.on_protoctl)) + self.Handler.register(m(command_name="SVS2MODE", func=self.on_svs2mode)) + self.Handler.register(m(command_name="SQUIT", func=self.on_squit)) + self.Handler.register(m(command_name="PART", func=self.on_part)) + self.Handler.register(m(command_name="VERSION", func=self.on_version_msg)) + self.Handler.register(m(command_name="UMODE2", func=self.on_umode2)) + self.Handler.register(m(command_name="NICK", func=self.on_nick)) + self.Handler.register(m(command_name="REPUTATION", func=self.on_reputation)) + self.Handler.register(m(command_name="SMOD", func=self.on_smod)) + self.Handler.register(m(command_name="SASL", func=self.on_sasl)) + self.Handler.register(m(command_name="MD", func=self.on_md)) + self.Handler.register(m(command_name="PRIVMSG", func=self.on_privmsg)) + + return None + def parse_server_msg(self, server_msg: list[str]) -> Optional[str]: """Parse the server message and return the command @@ -229,6 +257,42 @@ class Unrealircd6(IProtocol): self.__Irc.User.update_nickname(userObj.uid, newnickname) return None + def send_set_mode(self, modes: str, *, nickname: Optional[str] = None, channel_name: Optional[str] = None, params: Optional[str] = None) -> None: + """Set a mode to channel or to a nickname or for a user in a channel + + Args: + modes (str): The selected mode + nickname (Optional[str]): The nickname + channel_name (Optional[str]): The channel name + params (Optional[str]): Parameters like password. + """ + service_id = self.__Config.SERVICE_ID + + if modes[0] not in ['+', '-']: + self.__Logs.error(f"[MODE ERROR] The mode you have provided is missing the sign: {modes}") + return None + + if nickname and channel_name: + # :98KAAAAAB MODE #services +o defenderdev + if not self.__Irc.Channel.is_valid_channel(channel_name): + self.__Logs.error(f"[MODE ERROR] The channel is not valid: {channel_name}") + return None + self.send2socket(f":{service_id} MODE {channel_name} {modes} {nickname}") + return None + + if nickname and channel_name is None: + self.send2socket(f":{service_id} MODE {nickname} {modes}") + return None + + if nickname is None and channel_name: + if not self.__Irc.Channel.is_valid_channel(channel_name): + self.__Logs.error(f"[MODE ERROR] The channel is not valid: {channel_name}") + return None + self.send2socket(f":{service_id} MODE {channel_name} {modes} {params}") + return None + + return None + def send_squit(self, server_id: str, server_link: str, reason: str) -> None: if not reason: @@ -431,7 +495,7 @@ class Unrealircd6(IProtocol): reason (str): The reason for the quit """ user_obj = self.__Irc.User.get_user(uidornickname=uid) - reputationObj = self.__Irc.Reputation.get_Reputation(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) @@ -565,6 +629,103 @@ class Unrealircd6(IProtocol): return None + # ------------------------------------------------------------------------ + # COMMON IRC PARSER + # ------------------------------------------------------------------------ + + def parse_uid(self, serverMsg: list[str]) -> dict[str, str]: + """Parse UID and return dictionary. + >>> ['@s2s-md/geoip=cc=GBtag...', ':001', 'UID', 'albatros', '0', '1721564597', 'albatros', 'hostname...', '001HB8G04', '0', '+iwxz', 'hostname-vhost', 'hostname-vhost', 'MyZBwg==', ':...'] + Args: + serverMsg (list[str]): The UID ircd response + """ + umodes = str(serverMsg[10]) + remote_ip = self.__Base.decode_ip(str(serverMsg[13])) if 'S' not in umodes else '127.0.0.1' + + # Extract Geoip information + pattern = r'^.*geoip=cc=(\S{2}).*$' + geoip_match = match(pattern, serverMsg[0]) + geoip = geoip_match.group(1) if geoip_match else None + + response = { + 'uid': str(serverMsg[8]), + 'nickname': str(serverMsg[3]), + 'username': str(serverMsg[6]), + 'hostname': str(serverMsg[7]), + 'umodes': umodes, + 'vhost': str(serverMsg[11]), + 'ip': remote_ip, + 'realname': ' '.join(serverMsg[12:]).lstrip(':'), + 'geoip': geoip, + 'reputation_score': 0, + 'iswebirc': True if 'webirc' in serverMsg[0] else False, + 'iswebsocket': True if 'websocket' in serverMsg[0] else False + } + return response + + def parse_quit(self, serverMsg: list[str]) -> dict[str, str]: + """Parse quit and return dictionary. + >>> # ['@unrealtag...', ':001JKNY0N', 'QUIT', ':Quit:', '....'] + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The dictionary. + """ + scopy = serverMsg.copy() + if scopy[0].startswith('@'): + scopy.pop(0) + + response = { + "uid": scopy[0].replace(':', ''), + "reason": " ".join(scopy[3:]) + } + return response + + def parse_nick(self, serverMsg: list[str]) -> dict[str, str]: + """Parse nick changes and return dictionary. + >>> ['@unrealircd.org/geoip=FR;unrealircd.org/', ':001OOU2H3', 'NICK', 'WebIrc', '1703795844'] + + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + """ + scopy = serverMsg.copy() + if scopy[0].startswith('@'): + scopy.pop(0) + + response = { + "uid": scopy[0].replace(':', ''), + "newnickname": scopy[2], + "timestamp": scopy[3] + } + return response + + def parse_privmsg(self, serverMsg: list[str]) -> dict[str, str]: + """Parse PRIVMSG message. + >>> ['@....', ':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message'] + >>> [':97KAAAAAF', 'PRIVMSG', '98KAAAAAB', ':sasa'] + + Args: + serverMsg (list[str]): The server message to parse + + Returns: + dict[str, str]: The response as dictionary. + """ + scopy = serverMsg.copy() + if scopy[0].startswith('@'): + scopy.pop(0) + + response = { + "uid_sender": scopy[0].replace(':', ''), + "uid_reciever": self.__Irc.User.get_uid(scopy[2]), + "channel": scopy[2] if self.__Irc.Channel.is_valid_channel(scopy[2]) else None, + "message": " ".join(scopy[3:]) + } + return response + ##################### # HANDLE EVENTS # ##################### @@ -736,6 +897,7 @@ class Unrealircd6(IProtocol): self.__Irc.User.update_nickname(uid, newnickname) self.__Irc.Client.update_nickname(uid, newnickname) self.__Irc.Admin.update_nickname(uid, newnickname) + self.__Irc.Reputation.update(uid, newnickname) return None @@ -856,6 +1018,8 @@ class Unrealircd6(IProtocol): self.__Logs.info(f"# VERSION : {version} ") self.__Logs.info(f"################################################") + self.send_sjoin(self.__Config.SERVICE_CHANLOG) + if self.__Base.check_for_new_version(False): self.send_priv_msg( nick_from=self.__Config.SERVICE_NICKNAME, @@ -875,6 +1039,10 @@ class Unrealircd6(IProtocol): for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): module.class_instance.cmd(server_msg_copy) + # Join saved channels & load existing modules + self.__Irc.join_saved_channels() + self.__Irc.ModuleUtils.db_load_all_existing_modules(self.__Irc) + return None except IndexError as ie: self.__Logs.error(f"{__name__} - Key Error: {ie}") @@ -988,14 +1156,42 @@ class Unrealircd6(IProtocol): dnickname = self.__Config.SERVICE_NICKNAME dchanlog = self.__Config.SERVICE_CHANLOG GREEN = self.__Config.COLORS.green + RED = self.__Config.COLORS.red NOGC = self.__Config.COLORS.nogc + for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): + module.class_instance.cmd(serverMsg) + + # SASL authentication + # ['@s2s-md/..', ':001', 'UID', 'adator__', '0', '1755987444', '...', 'desktop-h1qck20.mshome.net', '001XLTT0U', '0', '+iwxz', '*', 'Clk-EC2256B2.mshome.net', 'rBKAAQ==', ':...'] + + uid = serverMsg[8] + nickname = serverMsg[3] + sasl_obj = self.__Irc.Sasl.get_sasl_obj(uid) + if sasl_obj: + if sasl_obj.auth_success: + self.__Irc.insert_db_admin(sasl_obj.client_uid, sasl_obj.username, sasl_obj.level, sasl_obj.language) + self.send_priv_msg(nick_from=dnickname, + msg=tr("[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, nickname, sasl_obj.username, dnickname), + channel=dchanlog) + self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Successfuly connected to %s", dnickname)) + else: + self.send_priv_msg(nick_from=dnickname, + msg=tr("[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s", RED, NOGC, nickname, sasl_obj.username), + channel=dchanlog) + self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Wrong password!")) + + # Delete sasl object! + self.__Irc.Sasl.delete_sasl_client(uid) + return None + + # If no sasl authentication then auto connect via fingerprint if self.__Irc.Admin.db_auth_admin_via_fingerprint(fingerprint, uid): admin = self.__Irc.Admin.get_admin(uid) account = admin.account if admin else '' self.send_priv_msg(nick_from=dnickname, - msg=tr("[ %sSASL AUTO AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, nickname, account, dnickname), - channel=dchanlog) + msg=tr("[ %sSASL AUTO AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, nickname, account, dnickname), + channel=dchanlog) self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Successfuly connected to %s", dnickname)) return None @@ -1254,7 +1450,7 @@ class Unrealircd6(IProtocol): except Exception as err: self.__Logs.error(f'General Error: {err}') - def on_sasl(self, serverMsg: list[str], psasl: 'Sasl') -> Optional['MSasl']: + def on_sasl(self, serverMsg: list[str]) -> Optional['MSasl']: """Handle SASL coming from a server Args: @@ -1267,7 +1463,7 @@ class Unrealircd6(IProtocol): # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '0014ZZH1F', 'S', 'EXTERNAL', 'zzzzzzzkey'] # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '00157Z26U', 'C', 'sasakey=='] # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '00157Z26U', 'D', 'A'] - + psasl = self.__Irc.Sasl sasl_enabled = False for smod in self.__Settings.SMOD_MODULES: if smod.name == 'sasl': @@ -1307,6 +1503,7 @@ class Unrealircd6(IProtocol): sasl_obj.fingerprint = str(sCopy[6]) self.send2socket(f":{self.__Config.SERVEUR_LINK} SASL {self.__Settings.MAIN_SERVER_HOSTNAME} {sasl_obj.client_uid} C +") + self.on_sasl_authentication_process(sasl_obj) return sasl_obj case 'C': @@ -1319,14 +1516,64 @@ class Unrealircd6(IProtocol): sasl_obj.username = username sasl_obj.password = password + self.on_sasl_authentication_process(sasl_obj) return sasl_obj elif sasl_obj.mechanisme == "EXTERNAL": sasl_obj.message_type = sasl_message_type + + self.on_sasl_authentication_process(sasl_obj) return sasl_obj except Exception as err: self.__Logs.error(f'General Error: {err}', exc_info=True) + def on_sasl_authentication_process(self, sasl_model: 'MSasl') -> bool: + s = sasl_model + if sasl_model: + def db_get_admin_info(*, username: Optional[str] = None, password: Optional[str] = None, fingerprint: Optional[str] = None) -> Optional[dict[str, Any]]: + if fingerprint: + mes_donnees = {'fingerprint': fingerprint} + query = f"SELECT user, level, language FROM {self.__Config.TABLE_ADMIN} WHERE fingerprint = :fingerprint" + else: + mes_donnees = {'user': username, 'password': self.__Utils.hash_password(password)} + query = f"SELECT user, level, language 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 user_from_db: + return {'user': user_from_db[0], 'level': user_from_db[1], 'language': user_from_db[2]} + else: + return None + + if s.message_type == 'C' and s.mechanisme == 'PLAIN': + # Connection via PLAIN + admin_info = db_get_admin_info(username=s.username, password=s.password) + if admin_info is not None: + s.auth_success = True + s.level = admin_info.get('level', 0) + s.language = admin_info.get('language', 'EN') + self.send2socket(f":{self.__Config.SERVEUR_LINK} SASL {self.__Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S") + self.send2socket(f":{self.__Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful") + else: + self.send2socket(f":{self.__Config.SERVEUR_LINK} SASL {self.__Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D F") + self.send2socket(f":{self.__Config.SERVEUR_LINK} 904 {s.username} :SASL authentication failed") + + elif s.message_type == 'S' and s.mechanisme == 'EXTERNAL': + # Connection using fingerprints + admin_info = db_get_admin_info(fingerprint=s.fingerprint) + + if admin_info is not None: + s.auth_success = True + s.level = admin_info.get('level', 0) + s.username = admin_info.get('user', None) + s.language = admin_info.get('language', 'EN') + self.send2socket(f":{self.__Config.SERVEUR_LINK} SASL {self.__Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S") + self.send2socket(f":{self.__Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful") + else: + # "904 :SASL authentication failed" + self.send2socket(f":{self.__Config.SERVEUR_LINK} SASL {self.__Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D F") + self.send2socket(f":{self.__Config.SERVEUR_LINK} 904 {s.username} :SASL authentication failed") + def on_md(self, serverMsg: list[str]) -> None: """Handle MD responses [':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...'] diff --git a/core/classes/rehash.py b/core/classes/rehash.py index 72a2465..3f85ed3 100644 --- a/core/classes/rehash.py +++ b/core/classes/rehash.py @@ -33,14 +33,8 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") - 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 = uplink.Loader.PFactory.get() 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() @@ -49,11 +43,19 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") - time.sleep(0.5) uplink.Logs.warning('-- Waiting for socket to close ...') + # Reload configuration + uplink.Loader.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model() + uplink.Loader.Base = uplink.Loader.BaseModule.Base(uplink.Loader) + + for mod in REHASH_MODULES: + importlib.reload(sys.modules[mod]) + + uplink.Protocol = uplink.Loader.PFactory.get() + uplink.Protocol.register_command() + 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: @@ -70,13 +72,13 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None: channel=uplink.Config.SERVICE_CHANLOG ) uplink.Utils = sys.modules['core.utils'] - 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 + uplink.Loader.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model() + uplink.Loader.Config.HSID = config_model_bakcup.HSID + uplink.Loader.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT + uplink.Loader.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART + uplink.Loader.Config.SSL_VERSION = config_model_bakcup.SSL_VERSION + uplink.Loader.Config.CURRENT_VERSION = config_model_bakcup.CURRENT_VERSION + uplink.Loader.Config.LATEST_VERSION = config_model_bakcup.LATEST_VERSION conf_bkp_dict: dict = config_model_bakcup.to_dict() config_dict: dict = uplink.Config.to_dict() @@ -105,8 +107,9 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None: 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.Loader.Base = uplink.Loader.BaseModule.Base(uplink.Loader) uplink.Protocol = uplink.Loader.PFactory.get() + uplink.Protocol.register_command() # Reload Service modules for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): diff --git a/core/classes/reputation.py b/core/classes/reputation.py index 859f62c..3ee0d47 100644 --- a/core/classes/reputation.py +++ b/core/classes/reputation.py @@ -53,7 +53,7 @@ class Reputation: bool: True if updated """ - reputation_obj = self.get_Reputation(uid) + reputation_obj = self.get_reputation(uid) if reputation_obj is None: return False @@ -89,7 +89,7 @@ class Reputation: return result - def get_Reputation(self, uidornickname: str) -> Optional[MReputation]: + def get_reputation(self, uidornickname: str) -> Optional[MReputation]: """Get The User Object model Args: @@ -116,7 +116,7 @@ class Reputation: str|None: Return the UID """ - reputation_obj = self.get_Reputation(uidornickname) + reputation_obj = self.get_reputation(uidornickname) if reputation_obj is None: return None @@ -132,7 +132,7 @@ class Reputation: Returns: str|None: the nickname """ - reputation_obj = self.get_Reputation(uidornickname) + reputation_obj = self.get_reputation(uidornickname) if reputation_obj is None: return None @@ -149,7 +149,7 @@ class Reputation: bool: True if exist """ - reputation_obj = self.get_Reputation(uidornickname) + reputation_obj = self.get_reputation(uidornickname) if isinstance(reputation_obj, MReputation): return True diff --git a/core/classes/settings.py b/core/classes/settings.py index 2eccdac..fa503aa 100644 --- a/core/classes/settings.py +++ b/core/classes/settings.py @@ -8,7 +8,6 @@ from core.definition import MSModule, MAdmin if TYPE_CHECKING: from core.classes.user import User - from core.classes.admin import Admin class Settings: """This Class will never be reloaded. @@ -25,7 +24,11 @@ class Settings: CONSOLE: bool = False MAIN_SERVER_HOSTNAME: str = None + MAIN_SERVER_ID: str = None + PROTOCTL_PREFIX_MODES_SIGNES : dict[str, str] = {} + PROTOCTL_PREFIX_SIGNES_MODES : dict[str, str] = {} PROTOCTL_USER_MODES: list[str] = [] + PROTOCTL_CHANNEL_MODES: list[str] = [] PROTOCTL_PREFIX: list[str] = [] SMOD_MODULES: list[MSModule] = [] @@ -42,7 +45,7 @@ class Settings: __INSTANCE_OF_USER_UTILS: Optional['User'] = None """Instance of the User Utils class""" - __CURRENT_ADMIN: Optional['MAdmin'] = None + __CURRENT_ADMIN: Optional['MAdmin'] = None """The Current Admin Object Model""" __LOGGER: Optional[Logger] = None @@ -79,6 +82,7 @@ class Settings: @property def global_translation(self) -> dict[str, list[list[str]]]: + """Get/set global translation variable""" return self.__TRANSLATION @global_translation.setter @@ -87,6 +91,7 @@ class Settings: @property def global_lang(self) -> str: + """Global default language.""" return self.__LANG @global_lang.setter @@ -103,6 +108,7 @@ class Settings: @property def current_admin(self) -> MAdmin: + """Current admin data model.""" return self.__CURRENT_ADMIN @current_admin.setter @@ -111,6 +117,7 @@ class Settings: @property def global_logger(self) -> Logger: + """Global logger Instance""" return self.__LOGGER @global_logger.setter diff --git a/core/classes/user.py b/core/classes/user.py index db0b50a..aff0963 100644 --- a/core/classes/user.py +++ b/core/classes/user.py @@ -55,7 +55,7 @@ class User: return False user_obj.nickname = new_nickname - + self.Logs.debug(f"UID ({uid}) has benn update with new nickname ({new_nickname}).") return True def update_mode(self, uidornickname: str, modes: str) -> bool: diff --git a/core/definition.py b/core/definition.py index 5e826af..d8a6e05 100644 --- a/core/definition.py +++ b/core/definition.py @@ -93,6 +93,7 @@ class MReputation(MainModel): umodes: str = None vhost: str = None fingerprint: str = None + tls_cipher: str = None isWebirc: bool = False isWebsocket: bool = False remote_ip: str = None @@ -368,4 +369,14 @@ class MSasl(MainModel): fingerprint: Optional[str] = None language: str = "EN" auth_success: bool = False - level: int = 0 \ No newline at end of file + level: int = 0 + +@dataclass +class MRegister: + command_name: str + func: Any + +@dataclass +class MIrcdCommand: + command_name: str + func: Any \ No newline at end of file diff --git a/core/irc.py b/core/irc.py index 689e8e9..3239c1b 100644 --- a/core/irc.py +++ b/core/irc.py @@ -165,10 +165,10 @@ class Irc: try: self.init_service_user() self.Protocol: 'IProtocol' = self.Loader.PFactory.get() + self.Protocol.register_command() 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 - time.sleep(3) + # self.join_saved_channels() # Join existing channels # self.ModuleUtils.db_load_all_existing_modules(self) while self.signal: @@ -219,7 +219,7 @@ class Irc: except ssl.SSLEOFError as soe: self.Logs.error(f"SSLEOFError: {soe}") except AttributeError as atte: - self.Logs.critical(f"AttributeError: {atte}") + self.Logs.critical(f"AttributeError: {atte}", exc_info=True) except Exception as e: self.Logs.critical(f"General Error: {e}", exc_info=True) @@ -260,9 +260,9 @@ class Irc: # This is only to reference the method return None - ############################################## + # -------------------------------------------- # FIN CONNEXION IRC # - ############################################## + # -------------------------------------------- def build_command(self, level: int, module_name: str, command_name: str, command_description: str) -> None: """This method build the commands variable @@ -489,116 +489,12 @@ class Irc: return None self.Logs.debug(f">> {self.Utils.hide_sensitive_data(original_response)}") - # parsed_protocol = self.Protocol.parse_server_msg(original_response.copy()) - pos, parsed_protocol = self.Protocol.get_ircd_protocol_poisition(cmd=original_response) - match parsed_protocol: - case 'PING': - self.Protocol.on_server_ping(serverMsg=original_response) + pos, parsed_protocol = self.Protocol.get_ircd_protocol_poisition(cmd=original_response, log=True) - case 'SERVER': - self.Protocol.on_server(serverMsg=original_response) - - case 'SJOIN': - self.Protocol.on_sjoin(serverMsg=original_response) - - case 'EOS': - self.Protocol.on_eos(serverMsg=original_response) - - case 'UID': - try: - self.Protocol.on_uid(serverMsg=original_response) - for module in self.ModuleUtils.model_get_loaded_modules().copy(): - module.class_instance.cmd(original_response) - - # SASL authentication - # ['@s2s-md/..', ':001', 'UID', 'adator__', '0', '1755987444', '...', 'desktop-h1qck20.mshome.net', '001XLTT0U', '0', '+iwxz', '*', 'Clk-EC2256B2.mshome.net', 'rBKAAQ==', ':...'] - dnickname = self.Config.SERVICE_NICKNAME - dchanlog = self.Config.SERVICE_CHANLOG - uid = original_response[8] - nickname = original_response[3] - sasl_obj = self.Sasl.get_sasl_obj(uid) - if sasl_obj: - if sasl_obj.auth_success: - self.insert_db_admin(sasl_obj.client_uid, sasl_obj.username, sasl_obj.level, sasl_obj.language) - self.Protocol.send_priv_msg(nick_from=dnickname, - msg=tr("[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, nickname, sasl_obj.username, dnickname), - channel=dchanlog) - self.Protocol.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Successfuly connected to %s", dnickname)) - else: - self.Protocol.send_priv_msg(nick_from=dnickname, - msg=tr("[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s", RED, NOGC, nickname, sasl_obj.username), - channel=dchanlog) - self.Protocol.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Wrong password!")) - - # Delete sasl object! - self.Sasl.delete_sasl_client(uid) - - return None - except Exception as err: - self.Logs.error(f'General Error: {err}') - - case 'QUIT': - self.Protocol.on_quit(serverMsg=original_response) - - case 'PROTOCTL': - self.Protocol.on_protoctl(serverMsg=original_response) - - case 'SVS2MODE': - self.Protocol.on_svs2mode(serverMsg=original_response) - - case 'SQUIT': - self.Protocol.on_squit(serverMsg=original_response) - - case 'PART': - self.Protocol.on_part(serverMsg=original_response) - - case 'VERSION': - self.Protocol.on_version_msg(serverMsg=original_response) - - case 'UMODE2': - self.Protocol.on_umode2(serverMsg=original_response) - - case 'NICK': - self.Protocol.on_nick(serverMsg=original_response) - - case 'REPUTATION': - self.Protocol.on_reputation(serverMsg=original_response) - - case 'SMOD': - self.Protocol.on_smod(original_response) - - case 'SASL': - sasl_response = self.Protocol.on_sasl(original_response, self.Sasl) - self.on_sasl_authentication_process(sasl_response) - - case 'MD': - self.Protocol.on_md(serverMsg=original_response) - - case 'PRIVMSG': - self.Protocol.on_privmsg(serverMsg=original_response) - - case 'SLOG': # TODO - self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") - - case 'PONG': # TODO - 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"[!] 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"[!] TO HANDLE: {parsed_protocol}") - - case '318': # TODO - #:irc.deb.biz.st 318 PyDefender IRCParis93 :End of /WHOIS list. - self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}") - - case None: - self.Logs.debug(f"[!] TO HANDLE: {original_response}") + for parsed in self.Protocol.Handler.get_ircd_commands(): + if parsed.command_name.upper() == parsed_protocol: + parsed.func(original_response) if len(original_response) > 2: if original_response[2] != 'UID': @@ -1154,7 +1050,8 @@ class Irc: self.Base.execute_periodic_action() for chan_name in self.Channel.UID_CHANNEL_DB: - self.Protocol.send_mode_chan(chan_name.name, '-l') + # self.Protocol.send_mode_chan(chan_name.name, '-l') + self.Protocol.send_set_mode('-l', channel_name=chan_name.name) for client in self.Client.CLIENT_DB: self.Protocol.send_svslogout(client) @@ -1207,7 +1104,7 @@ class Irc: self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, - msg=tr('%s - %sNot Loaded%s', module, GREEN, NOGC) + msg=tr('%s - %sNot Loaded%s', module, RED, NOGC) ) case 'show_timers': diff --git a/defender.py b/defender.py index 6b5e1bc..81ec0f5 100644 --- a/defender.py +++ b/defender.py @@ -1,7 +1,7 @@ from core import installation ############################################# -# @Version : 6.2 # +# @Version : 6.3 # # Requierements : # # Python3.10 or higher # # SQLAlchemy, requests, psutil # diff --git a/mods/clone/mod_clone.py b/mods/clone/mod_clone.py index 29a1dd6..ee73e63 100644 --- a/mods/clone/mod_clone.py +++ b/mods/clone/mod_clone.py @@ -78,11 +78,11 @@ class Clone: self.__load_module_configuration() self.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL) - self.Protocol.send_join_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL) + self.Protocol.send_sjoin(self.Config.CLONE_CHANNEL) + self.Protocol.send_set_mode('+o', nickname=self.Config.SERVICE_NICKNAME, channel_name=self.Config.CLONE_CHANNEL) + self.Protocol.send_set_mode('+nts', channel_name=self.Config.CLONE_CHANNEL) + self.Protocol.send_set_mode('+k', channel_name=self.Config.CLONE_CHANNEL, params=self.Config.CLONE_CHANNEL_PASSWORD) - self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {self.Config.CLONE_CHANNEL} +o {self.Config.SERVICE_NICKNAME}") - self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +nts") - self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +k {self.Config.CLONE_CHANNEL_PASSWORD}") def __create_tables(self) -> None: """Methode qui va créer la base de donnée si elle n'existe pas. @@ -127,8 +127,8 @@ class Clone: self.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB) self.Channel.db_query_channel(action='del', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL) - self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -nts") - self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -k {self.Config.CLONE_CHANNEL_PASSWORD}") + self.Protocol.send_set_mode('-nts', channel_name=self.Config.CLONE_CHANNEL) + self.Protocol.send_set_mode('-k', channel_name=self.Config.CLONE_CHANNEL) self.Protocol.send_part_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL) self.Irc.Commands.drop_command_by_module(self.module_name) @@ -148,7 +148,8 @@ class Clone: match command: case 'PRIVMSG': - return self.Utils.handle_on_privmsg(self, cmd) + self.Utils.handle_on_privmsg(self, cmd) + return None case 'QUIT': return None diff --git a/mods/clone/utils.py b/mods/clone/utils.py index e2b5759..495252e 100644 --- a/mods/clone/utils.py +++ b/mods/clone/utils.py @@ -174,17 +174,17 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def return True -def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]): +def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]) -> None: - uid_sender = uplink.Irc.Utils.clean_uid(srvmsg[1]) + parser = uplink.Protocol.parse_privmsg(srvmsg) + uid_sender = uplink.Irc.Utils.clean_uid(parser.get('uid_sender', None)) senderObj = uplink.User.get_user(uid_sender) - if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT: - return - - if not senderObj is None: - senderMsg = ' '.join(srvmsg[4:]) - clone_obj = uplink.Clone.get_clone(srvmsg[3]) + if senderObj is not None: + if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT: + return + senderMsg = parser.get('message', None) + clone_obj = uplink.Clone.get_clone(parser.get('uid_reciever', None)) if clone_obj is None: return @@ -196,3 +196,5 @@ def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]): msg=final_message, channel=uplink.Config.CLONE_CHANNEL ) + + return None diff --git a/mods/command/utils.py b/mods/command/utils.py index b7a40dc..d7ef806 100644 --- a/mods/command/utils.py +++ b/mods/command/utils.py @@ -134,17 +134,20 @@ def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str] return False if len(cmd) == 1: - uplink.Protocol.send2socket(f":{dnickname} MODE {channel_name} {mode} {client}") + # uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {client}") + uplink.Protocol.send_set_mode(mode, nickname=client, channel_name=channel_name) return None # deop nickname if len(cmd) == 2: nickname = cmd[1] - uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}") + # uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}") + uplink.Protocol.send_set_mode(mode, nickname=nickname, channel_name=channel_name) return None nickname = cmd[2] - uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}") + # uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}") + uplink.Protocol.send_set_mode(mode, nickname=nickname, channel_name=channel_name) return None def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None: diff --git a/mods/defender/mod_defender.py b/mods/defender/mod_defender.py index b861607..c5a674d 100644 --- a/mods/defender/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -365,7 +365,7 @@ class Defender: release_code = cmd[1] jailed_nickname = self.User.get_nickname(fromuser) jailed_UID = self.User.get_uid(fromuser) - get_reputation = self.Reputation.get_Reputation(jailed_UID) + get_reputation = self.Reputation.get_reputation(jailed_UID) if get_reputation is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" No code is requested ...") @@ -551,7 +551,7 @@ class Defender: msg=f"This nickname ({str(cmd[2])}) is not connected to the network!") return None - client_to_release = self.Reputation.get_Reputation(client_obj.uid) + client_to_release = self.Reputation.get_reputation(client_obj.uid) if client_to_release is None: p.send_notice(nick_from=dnickname, diff --git a/mods/defender/utils.py b/mods/defender/utils.py index bf85d0d..5c3d155 100644 --- a/mods/defender/utils.py +++ b/mods/defender/utils.py @@ -87,7 +87,7 @@ def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]): return if confmodel.reputation == 1: - get_reputation = irc.Reputation.get_Reputation(parsed_UID) + get_reputation = irc.Reputation.get_reputation(parsed_UID) if parsed_chan != gconfig.SALON_JAIL: p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b ~security-group:unknown-users") @@ -138,18 +138,20 @@ def handle_on_slog(uplink: 'Defender', srvmsg: list[str]): return None def handle_on_nick(uplink: 'Defender', srvmsg: list[str]): - """_summary_ + """Handle nickname changes. >>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'newnickname', '1754663712'] + >>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740'] Args: irc_instance (Irc): The Irc instance srvmsg (list[str]): The Server MSG confmodel (ModConfModel): The Module Configuration """ - uid = uplink.Loader.Utils.clean_uid(str(srvmsg[1])) p = uplink.Protocol + parser = p.parse_nick(srvmsg) + uid = uplink.Loader.Utils.clean_uid(parser.get('uid', None)) confmodel = uplink.ModConfig - get_reputation = uplink.Reputation.get_Reputation(uid) + get_reputation = uplink.Reputation.get_reputation(uid) jail_salon = uplink.Config.SALON_JAIL service_id = uplink.Config.SERVICE_ID @@ -159,7 +161,7 @@ def handle_on_nick(uplink: 'Defender', srvmsg: list[str]): # Update the new nickname oldnick = get_reputation.nickname - newnickname = srvmsg[3] + newnickname = parser.get('newnickname', None) get_reputation.nickname = newnickname # If ban in all channel is ON then unban old nickname an ban the new nickname @@ -170,20 +172,21 @@ def handle_on_nick(uplink: 'Defender', srvmsg: list[str]): p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*") def handle_on_quit(uplink: 'Defender', srvmsg: list[str]): - """_summary_ + """Handle on quit message >>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', 'quit message'] Args: uplink (Irc): The Defender Module instance srvmsg (list[str]): The Server MSG """ p = uplink.Protocol + parser = p.parse_quit(srvmsg) confmodel = uplink.ModConfig ban_all_chan = uplink.Base.int_if_possible(confmodel.reputation_ban_all_chan) - final_UID = uplink.Loader.Utils.clean_uid(str(srvmsg[1])) + final_UID = uplink.Loader.Utils.clean_uid(str(parser.get('uid', None))) jail_salon = uplink.Config.SALON_JAIL service_id = uplink.Config.SERVICE_ID - get_user_reputation = uplink.Reputation.get_Reputation(final_UID) + get_user_reputation = uplink.Reputation.get_reputation(final_UID) if get_user_reputation is not None: final_nickname = get_user_reputation.nickname @@ -204,6 +207,7 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]): uplink (Defender): The Defender instance srvmsg (list[str]): The Server MSG """ + parser_uid = uplink.Protocol.parse_uid(srvmsg) gconfig = uplink.Config irc = uplink.Irc confmodel = uplink.ModConfig @@ -213,10 +217,10 @@ 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(parser_uid.get('uid', None)) if _User is None: - irc.Logs.warning(f'This UID: [{srvmsg[8]}] is not available please check why') + irc.Logs.warning(f'This UID: [{parser_uid.get("uid", None)}] is not available please check why') return # If user is not service or IrcOp then scan them @@ -249,7 +253,8 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]): #################### # ACTION FUNCTIONS # #################### - +# [:] UID []+ : +# [:] UID nickname hopcount timestamp username hostname uid servicestamp umodes virthost cloakedhost ip :gecos def action_on_flood(uplink: 'Defender', srvmsg: list[str]): confmodel = uplink.ModConfig @@ -318,7 +323,7 @@ def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ): p = uplink.Protocol confmodel = uplink.ModConfig - get_reputation = irc.Reputation.get_Reputation(jailed_uid) + get_reputation = irc.Reputation.get_reputation(jailed_uid) if get_reputation is None: irc.Logs.warning(f'UID {jailed_uid} has not been found') @@ -404,7 +409,7 @@ def action_apply_reputation_santions(uplink: 'Defender') -> None: # Suppression des éléments dans {UID_DB} et {REPUTATION_DB} for chan in irc.Channel.UID_CHANNEL_DB: if chan.name != salon_jail and ban_all_chan == 1: - get_user_reputation = irc.Reputation.get_Reputation(uid) + get_user_reputation = irc.Reputation.get_reputation(uid) p.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*") # Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}. diff --git a/version.json b/version.json index 3a5286e..c641feb 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "6.2.6", + "version": "6.3.0", "requests": "2.32.3", "psutil": "6.0.0", From a043a58f452a4b344d8515e40b8cde780ff3a476 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:49:21 +0200 Subject: [PATCH 4/6] Update to the 3.0.0 V --- core/classes/admin.py | 5 + core/classes/channel.py | 19 +- core/classes/client.py | 20 +- core/classes/commands.py | 4 + core/classes/protocols/command_handler.py | 6 + core/classes/protocols/factory.py | 5 + core/classes/protocols/inspircd.py | 418 +++++++++++----------- core/classes/protocols/interface.py | 144 ++++---- core/classes/protocols/unreal6.py | 53 +-- core/classes/rehash.py | 21 +- core/classes/reputation.py | 9 +- core/classes/sasl.py | 11 +- core/classes/settings.py | 6 +- core/classes/translation.py | 6 +- core/irc.py | 2 +- core/module.py | 1 + mods/jsonrpc/mod_jsonrpc.py | 93 ++--- mods/jsonrpc/threads.py | 27 +- 18 files changed, 435 insertions(+), 415 deletions(-) diff --git a/core/classes/admin.py b/core/classes/admin.py index 5ff95d8..7479e36 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -9,6 +9,11 @@ class Admin: UID_ADMIN_DB: list[MAdmin] = [] def __init__(self, loader: 'Loader') -> None: + """ + + Args: + loader (Loader): The Loader Instance. + """ self.Logs = loader.Logs self.Base = loader.Base self.Setting = loader.Settings diff --git a/core/classes/channel.py b/core/classes/channel.py index 76acb11..0bcb0fb 100644 --- a/core/classes/channel.py +++ b/core/classes/channel.py @@ -11,14 +11,16 @@ class Channel: """List that contains all the Channels objects (ChannelModel) """ - def __init__(self, loader: 'Loader') -> None: + def __init__(self, loader: 'Loader'): + """ + Args: + loader (Loader): The Loader Instance + """ self.Logs = loader.Logs self.Base = loader.Base self.Utils = loader.Utils - return None - def insert(self, new_channel: 'MChannel') -> bool: """This method will insert a new channel and if the channel exist it will update the user list (uids) @@ -110,6 +112,7 @@ class Channel: return result except ValueError as ve: self.Logs.error(f'{ve}') + return False def delete_user_from_all_channel(self, uid:str) -> bool: """Delete a client from all channels @@ -134,6 +137,7 @@ class Channel: return result except ValueError as ve: self.Logs.error(f'{ve}') + return False def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool: """Add a client to a channel @@ -226,16 +230,18 @@ class Channel: return False pattern = fr'^#' - isChannel = findall(pattern, channel_to_check) + is_channel = findall(pattern, channel_to_check) - if not isChannel: + if not is_channel: return False else: return True except TypeError as te: self.Logs.error(f'TypeError: [{channel_to_check}] - {te}') + return False except Exception as err: self.Logs.error(f'Error Not defined: {err}') + return False def db_query_channel(self, action: Literal['add','del'], module_name: str, channel_name: str) -> bool: """You can add a channel or delete a channel. @@ -282,8 +288,7 @@ class Channel: else: return False - case _: - return False except Exception as err: self.Logs.error(err) + return False diff --git a/core/classes/client.py b/core/classes/client.py index f7afb41..2ea01c3 100644 --- a/core/classes/client.py +++ b/core/classes/client.py @@ -10,7 +10,11 @@ class Client: CLIENT_DB: list['MClient'] = [] def __init__(self, loader: 'Loader'): + """ + Args: + loader (Loader): The Loader instance. + """ self.Logs = loader.Logs self.Base = loader.Base @@ -34,12 +38,12 @@ class Client: return True - def update_nickname(self, uid: str, newNickname: str) -> bool: + def update_nickname(self, uid: str, new_nickname: str) -> bool: """Update the nickname starting from the UID Args: uid (str): UID of the user - newNickname (str): New nickname + new_nickname (str): New nickname Returns: bool: True if updated @@ -49,7 +53,7 @@ class Client: if user_obj is None: return False - user_obj.nickname = newNickname + user_obj.nickname = new_nickname return True @@ -181,7 +185,7 @@ class Client: return client_obj.to_dict() - def is_exist(self, uidornikname: str) -> bool: + def is_exist(self, uidornickname: str) -> bool: """Check if the UID or the nickname exist in the USER DB Args: @@ -190,7 +194,7 @@ class Client: Returns: bool: True if exist """ - user_obj = self.get_Client(uidornickname=uidornikname) + user_obj = self.get_Client(uidornickname=uidornickname) if user_obj is None: return False @@ -231,9 +235,9 @@ class Client: """ pattern = fr'[:|@|%|\+|~|\*]*' - parsed_UID = sub(pattern, '', uid) + parsed_uid = sub(pattern, '', uid) - if not parsed_UID: + if not parsed_uid: return None - return parsed_UID \ No newline at end of file + return parsed_uid \ No newline at end of file diff --git a/core/classes/commands.py b/core/classes/commands.py index f47bef4..29863a9 100644 --- a/core/classes/commands.py +++ b/core/classes/commands.py @@ -9,6 +9,10 @@ class Command: DB_COMMANDS: list['MCommand'] = [] def __init__(self, loader: 'Loader'): + """ + Args: + loader (Loader): The Loader instance. + """ self.Loader = loader self.Base = loader.Base self.Logs = loader.Logs diff --git a/core/classes/protocols/command_handler.py b/core/classes/protocols/command_handler.py index b06b9e6..4c02d93 100644 --- a/core/classes/protocols/command_handler.py +++ b/core/classes/protocols/command_handler.py @@ -10,6 +10,11 @@ class CommandHandler: DB_SUBSCRIBE: list = [] def __init__(self, loader: 'Loader'): + """Init method + + Args: + loader (Loader): The loader Object + """ self.__Logs = loader.Logs def register(self, ircd_command_model: 'MIrcdCommand') -> None: @@ -25,6 +30,7 @@ class CommandHandler: return None else: self.__Logs.debug(f'[IRCD COMMAND HANDLER] This IRCD command ({ircd_command.command_name}) already exist in the handler.') + return None def get_registred_ircd_command(self, command_name: str) -> Optional['MIrcdCommand']: """Get the registred IRCD command model diff --git a/core/classes/protocols/factory.py b/core/classes/protocols/factory.py index 32ac63a..7ccf267 100644 --- a/core/classes/protocols/factory.py +++ b/core/classes/protocols/factory.py @@ -9,6 +9,11 @@ if TYPE_CHECKING: class ProtocolFactorty: def __init__(self, uplink: 'Irc'): + """ProtocolFactory init. + + Args: + uplink (Irc): The Irc object + """ self.__Config = uplink.Config self.__uplink = uplink diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index 531227d..a4957b2 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -14,16 +14,21 @@ if TYPE_CHECKING: class Inspircd(IProtocol): - def __init__(self, ircInstance: 'Irc'): + def __init__(self, uplink: 'Irc'): + """ + + Args: + uplink (Irc): The Irc object + """ self.name = 'InspIRCd-4' self.protocol_version = 1206 - self.__Irc = ircInstance - self.__Config = ircInstance.Config - self.__Base = ircInstance.Base - self.__Utils = ircInstance.Loader.Utils - self.__Settings = ircInstance.Settings - self.__Logs = ircInstance.Loader.Logs + self.__Irc = uplink + self.__Config = uplink.Config + self.__Base = uplink.Base + self.__Utils = uplink.Loader.Utils + self.__Settings = uplink.Settings + self.__Logs = uplink.Loader.Logs self.known_protocol: set[str] = {'UID', 'ERROR', 'PRIVMSG', 'SINFO', 'FJOIN', 'PING', 'PONG', @@ -32,7 +37,7 @@ class Inspircd(IProtocol): 'MODE', 'QUIT', 'SQUIT', 'VERSION'} - self.Handler = CommandHandler(ircInstance.Loader) + self.Handler = CommandHandler(uplink.Loader) self.__Logs.info(f"[PROTOCOL] Protocol [{__name__}] loaded!") @@ -41,6 +46,7 @@ class Inspircd(IProtocol): Args: cmd (list[str]): The server response + log (bool): if True then print logs Returns: tuple[int, Optional[str]]: The position and the command. @@ -52,7 +58,7 @@ class Inspircd(IProtocol): if log: self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") - return (-1, None) + return -1, None def register_command(self): m = self.__Irc.Loader.Definition.MIrcdCommand @@ -74,7 +80,8 @@ class Inspircd(IProtocol): """Envoit les commandes à envoyer au serveur. Args: - string (Str): contient la commande à envoyer au serveur. + message (str): The message to send to the socket. + print_log (bool): if True print the log. """ try: with self.__Base.lock: @@ -97,7 +104,7 @@ class Inspircd(IProtocol): except OSError as oe: self.__Logs.error(f"OSError: {oe} - {message}") if oe.errno == 10053: - sys.exit(oe) + sys.exit(oe.__str__()) except AttributeError as ae: self.__Logs.critical(f"Attribute Error: {ae}") @@ -111,23 +118,23 @@ class Inspircd(IProtocol): nick_to (str, optional): The reciever nickname. Defaults to None. """ 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 + 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 not None else None - if User_from is None: + if user_from is None: self.__Logs.error(f"The sender nickname [{nick_from}] do not exist") return None if not channel is None: for i in range(0, len(str(msg)), batch_size): batch = str(msg)[i:i+batch_size] - self.send2socket(f":{User_from.uid} PRIVMSG {channel} :{batch}") + self.send2socket(f":{user_from.uid} PRIVMSG {channel} :{batch}") if not nick_to is None: for i in range(0, len(str(msg)), batch_size): batch = str(msg)[i:i+batch_size] - self.send2socket(f":{nick_from} PRIVMSG {User_to.uid} :{batch}") + self.send2socket(f":{nick_from} PRIVMSG {user_to.uid} :{batch}") except Exception as err: self.__Logs.error(f"General Error: {err}") @@ -141,16 +148,16 @@ class Inspircd(IProtocol): """ 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: + 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") return None for i in range(0, len(str(msg)), batch_size): batch = str(msg)[i:i+batch_size] - self.send2socket(f":{User_from.uid} NOTICE {User_to.uid} :{batch}") + self.send2socket(f":{user_from.uid} NOTICE {user_to.uid} :{batch}") except Exception as err: self.__Logs.error(f"General Error: {err}") @@ -159,39 +166,34 @@ class Inspircd(IProtocol): """Créer le link et envoyer les informations nécessaires pour la connexion au serveur. """ - - nickname = self.__Config.SERVICE_NICKNAME - username = self.__Config.SERVICE_USERNAME - realname = self.__Config.SERVICE_REALNAME - chan = self.__Config.SERVICE_CHANLOG - info = self.__Config.SERVICE_INFO - smodes = self.__Config.SERVICE_SMODES - cmodes = self.__Config.SERVICE_CMODES - umodes = self.__Config.SERVICE_UMODES - host = self.__Config.SERVICE_HOST + service_id = self.__Config.SERVICE_ID + service_nickname = self.__Config.SERVICE_NICKNAME + service_username = self.__Config.SERVICE_USERNAME + service_realname = self.__Config.SERVICE_REALNAME + service_info = self.__Config.SERVICE_INFO + service_smodes = self.__Config.SERVICE_SMODES + service_hostname = self.__Config.SERVICE_HOST service_name = self.__Config.SERVICE_NAME - password = self.__Config.SERVEUR_PASSWORD - link = self.__Config.SERVEUR_LINK + server_password = self.__Config.SERVEUR_PASSWORD + server_link = self.__Config.SERVEUR_LINK server_id = self.__Config.SERVEUR_ID - service_id = self.__Config.SERVICE_ID - server_hostname = self.__Config.SERVEUR_HOSTNAME + server_hostname = self.__Settings.MAIN_SERVER_HOSTNAME = self.__Config.SERVEUR_HOSTNAME version = self.__Config.CURRENT_VERSION unixtime = self.__Utils.get_unixtime() - self.__Settings.MAIN_SERVER_HOSTNAME = server_hostname self.send2socket(f"CAPAB START {self.protocol_version}") self.send2socket(f"CAPAB MODULES :services") self.send2socket(f"CAPAB MODSUPPORT :") self.send2socket(f"CAPAB CAPABILITIES :NICKMAX=30 CHANMAX=64 MAXMODES=20 IDENTMAX=10 MAXQUIT=255 MAXTOPIC=307 MAXKICK=255 MAXREAL=128 MAXAWAY=200 MAXHOST=64 MAXLINE=512 CASEMAPPING=ascii GLOBOPS=0") self.send2socket(f"CAPAB END") - self.send2socket(f"SERVER {link} {password} {server_id} :{info}") + self.send2socket(f"SERVER {server_link} {server_password} {server_id} :{service_info}") self.send2socket(f"BURST {unixtime}") self.send2socket(f":{server_id} SINFO version :{service_name}-{version.split('.')[0]}. {server_hostname} :") - self.send2socket(f":{server_id} SINFO fullversion :{service_name}-{version}. {host} :") + self.send2socket(f":{server_id} SINFO fullversion :{service_name}-{version}. {service_hostname} :") self.send2socket(f":{server_id} SINFO rawversion :{service_name}-{version}") - self.send_uid(nickname, username, host, service_id, smodes, host, "127.0.0.1", realname) + self.send_uid(service_nickname, service_username, service_hostname, service_id, service_smodes, service_hostname, "127.0.0.1", service_realname) self.send2socket(f":{server_id} ENDBURST") # self.send_sjoin(chan) @@ -216,6 +218,7 @@ class Inspircd(IProtocol): modes (str): The selected mode nickname (Optional[str]): The nickname channel_name (Optional[str]): The channel name + params (Optional[str]): Params to pass to the mode """ service_id = self.__Config.SERVICE_ID params = '' if params is None else params @@ -304,22 +307,23 @@ class Inspircd(IProtocol): self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[service_id])) return None - def send_quit(self, uid: str, reason: str, print_log: True) -> None: + def send_quit(self, uid: str, reason: str, print_log: bool = True) -> None: """Send quit message Args: - uidornickname (str): The UID or the Nickname + uid (str): The UID. reason (str): The reason for the quit + print_log (bool): If True then print logs """ user_obj = self.__Irc.User.get_user(uidornickname=uid) - reputationObj = self.__Irc.Reputation.get_reputation(uidornickname=uid) + reputation_obj = 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 reputationObj is None: - self.__Irc.Reputation.delete(reputationObj.uid) + if not reputation_obj is None: + self.__Irc.Reputation.delete(reputation_obj.uid) if not self.__Irc.Channel.delete_user_from_all_channel(uid): self.__Logs.error(f"The UID [{uid}] has not been deleted from all channels") @@ -382,20 +386,20 @@ class Inspircd(IProtocol): print_log (bool, optional): Write logs. Defaults to True. """ - userObj = self.__Irc.User.get_user(uidornickname) - passwordChannel = password if not password is None else '' + user_obj = self.__Irc.User.get_user(uidornickname) + password_channel = password if not password is None else '' - if userObj is None: + if user_obj is None: return None if not self.__Irc.Channel.is_valid_channel(channel): self.__Logs.error(f"The channel [{channel}] is not valid") return None - self.send2socket(f":{userObj.uid} FJOIN {channel} {self.__Utils.get_unixtime()} :,{userObj.uid} {passwordChannel}", print_log=print_log) + self.send2socket(f":{user_obj.uid} FJOIN {channel} {self.__Utils.get_unixtime()} :,{user_obj.uid} {password_channel}", print_log=print_log) # Add defender to the channel uids list - self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[userObj.uid])) + self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[user_obj.uid])) return None def send_part_chan(self, uidornickname:str, channel: str, print_log: bool = True) -> None: @@ -407,9 +411,9 @@ class Inspircd(IProtocol): print_log (bool, optional): Write logs. Defaults to True. """ - userObj = self.__Irc.User.get_user(uidornickname) + user_obj = self.__Irc.User.get_user(uidornickname) - if userObj is None: + if user_obj is None: self.__Logs.error(f"The user [{uidornickname}] is not valid") return None @@ -417,10 +421,10 @@ class Inspircd(IProtocol): self.__Logs.error(f"The channel [{channel}] is not valid") return None - self.send2socket(f":{userObj.uid} PART {channel}", print_log=print_log) + self.send2socket(f":{user_obj.uid} PART {channel}", print_log=print_log) # Add defender to the channel uids list - self.__Irc.Channel.delete_user_from_channel(channel, userObj.uid) + self.__Irc.Channel.delete_user_from_channel(channel, user_obj.uid) return None def send_unkline(self, nickname:str, hostname: str) -> None: @@ -438,26 +442,23 @@ class Inspircd(IProtocol): # RECIEVED IRC MESSAGES # ------------------------------------------------------------------------ - def on_umode2(self, serverMsg: list[str]) -> None: + def on_umode2(self, server_msg: list[str]) -> None: """Handle umode2 coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: # [':adator_', 'UMODE2', '-iwx'] - userObj = self.__Irc.User.get_user(str(serverMsg[0]).lstrip(':')) - userMode = serverMsg[2] + user_obj = self.__Irc.User.get_user(str(server_msg[0]).lstrip(':')) + user_mode = server_msg[2] - if userObj is None: # If user is not created + if user_obj is None: # If user is not created return None - # save previous user modes - old_umodes = userObj.umodes - # TODO : User object should be able to update user modes - if self.__Irc.User.update_mode(userObj.uid, userMode): + if self.__Irc.User.update_mode(user_obj.uid, user_mode): return None # self.__Logs.debug(f"Updating user mode for [{userObj.nickname}] [{old_umodes}] => [{userObj.umodes}]") @@ -468,15 +469,15 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_quit(self, serverMsg: list[str]) -> None: + def on_quit(self, server_msg: list[str]) -> None: """Handle quit coming from a server >> [':97KAAAAAZ', 'QUIT', ':Quit:', '....'] Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: - uid_who_quit = str(serverMsg[0]).lstrip(':') + uid_who_quit = str(server_msg[0]).lstrip(':') self.__Irc.Channel.delete_user_from_all_channel(uid_who_quit) self.__Irc.User.delete(uid_who_quit) @@ -489,15 +490,15 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_squit(self, serverMsg: list[str]) -> None: + def on_squit(self, server_msg: list[str]) -> None: """Handle squit coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ # ['@msgid=QOEolbRxdhpVW5c8qLkbAU;time=2024-09-21T17:33:16.547Z', 'SQUIT', 'defender.deb.biz.st', ':Connection', 'closed'] - server_hostname = serverMsg[2] + server_hostname = server_msg[2] uid_to_delete = None for s_user in self.__Irc.User.UID_DB: if s_user.hostname == server_hostname and 'S' in s_user.umodes: @@ -511,17 +512,17 @@ class Inspircd(IProtocol): return None - def on_protoctl(self, serverMsg: list[str]) -> None: + def on_protoctl(self, server_msg: list[str]) -> None: """Handle CAPAB coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ # ['CAPAB', 'CHANMODES', ':list:ban=b', 'param-set:limit=l', 'param:key=k', 'prefix:10000:voice=+v', 'prefix:30000:op=@o', 'prefix:50000:founder=~q', # 'simple:c_registered=r', 'simple:inviteonly=i', 'simple:moderated=m', 'simple:noextmsg=n', 'simple:private=p', # 'simple:secret=s', 'simple:sslonly=z', 'simple:topiclock=t'] - scopy = serverMsg.copy() + scopy = server_msg.copy() # Get Chan modes. if scopy[1] == 'CHANMODES': @@ -558,18 +559,18 @@ class Inspircd(IProtocol): return None - def on_nick(self, serverMsg: list[str]) -> None: + def on_nick(self, server_msg: list[str]) -> None: """Handle nick coming from a server new nickname Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: # [':97KAAAAAF', 'NICK', 'test', '1757370509'] # Changement de nickname - scopy = serverMsg.copy() + scopy = server_msg.copy() if scopy[0].startswith('@'): scopy.pop(0) @@ -586,20 +587,20 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_sjoin(self, serverMsg: list[str]) -> None: + def on_sjoin(self, server_msg: list[str]) -> None: """Handle sjoin coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: # [':97K', 'FJOIN', '#services', '1757156589', '+nt', ':,97KAAAAA2:22', 'o,97KAAAAAA:2'] - channel = str(serverMsg[2]).lower() + channel = str(server_msg[2]).lower() list_users:list = [] # Find uid's - for uid in serverMsg: + for uid in server_msg: matches = findall(r',([0-9A-Z]+):', uid) list_users.extend(matches) @@ -619,15 +620,15 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_endburst(self, serverMsg: list[str]) -> None: + def on_endburst(self, server_msg: list[str]) -> None: """Handle EOS coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: # [':97K', 'ENDBURST'] - scopy = serverMsg.copy() + scopy = server_msg.copy() hsid = str(scopy[0]).replace(':','') if hsid == self.__Config.HSID: if self.__Config.DEFENDER_INIT == 1: @@ -693,18 +694,18 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_part(self, serverMsg: list[str]) -> None: + def on_part(self, server_msg: list[str]) -> None: """Handle part coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: # [':97KAAAAA2', 'PART', '#v', ':"Closing', 'Window"'] - uid = str(serverMsg[0]).lstrip(':') - channel = str(serverMsg[2]).lower() - reason = str(' '.join(serverMsg[3:])) + uid = str(server_msg[0]).lstrip(':') + channel = str(server_msg[2]).lower() + # reason = str(' '.join(server_msg[3:])) self.__Irc.Channel.delete_user_from_channel(channel, uid) return None @@ -714,39 +715,39 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_uid(self, serverMsg: list[str]) -> None: + def on_uid(self, server_msg: list[str]) -> None: """Handle uid message coming from the server [:] UID []+ : [':97K', 'UID', '97KAAAAAB', '1756928055', 'adator_', '172.18.128.1', '172.18.128.1', '...', '...', '172.18.128.1', '1756928055', '+', ':...'] Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: - RED = self.__Config.COLORS.red - GREEN = self.__Config.COLORS.green - NOGC = self.__Config.COLORS.nogc - isWebirc = True if 'webirc' in serverMsg[0] else False - isWebsocket = True if 'websocket' in serverMsg[0] else False + red = self.__Config.COLORS.red + green = self.__Config.COLORS.green + nogc = self.__Config.COLORS.nogc + is_webirc = True if 'webirc' in server_msg[0] else False + is_websocket = True if 'websocket' in server_msg[0] else False - uid = str(serverMsg[2]) - nickname = str(serverMsg[4]) - username = str(serverMsg[7]) - hostname = str(serverMsg[5]) - umodes = str(serverMsg[11]) - vhost = str(serverMsg[6]) + uid = str(server_msg[2]) + nickname = str(server_msg[4]) + username = str(server_msg[7]) + hostname = str(server_msg[5]) + umodes = str(server_msg[11]) + vhost = str(server_msg[6]) if not 'S' in umodes: # remote_ip = self.__Base.decode_ip(str(serverMsg[9])) - remote_ip = str(serverMsg[9]) + remote_ip = str(server_msg[9]) else: remote_ip = '127.0.0.1' # extract realname - realname = ' '.join(serverMsg[12:]).lstrip(':') + realname = ' '.join(server_msg[12:]).lstrip(':') # Extract Geoip information pattern = r'^.*geoip=cc=(\S{2}).*$' - geoip_match = match(pattern, serverMsg[0]) + geoip_match = match(pattern, server_msg[0]) if geoip_match: geoip = geoip_match.group(1) @@ -764,8 +765,8 @@ class Inspircd(IProtocol): hostname=hostname, umodes=umodes, vhost=vhost, - isWebirc=isWebirc, - isWebsocket=isWebsocket, + isWebirc=is_webirc, + isWebsocket=is_websocket, remote_ip=remote_ip, geoip=geoip, score_connexion=score_connexion, @@ -774,7 +775,7 @@ class Inspircd(IProtocol): ) for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): - module.class_instance.cmd(serverMsg) + module.class_instance.cmd(server_msg) # SASL authentication dnickname = self.__Config.SERVICE_NICKNAME @@ -786,12 +787,12 @@ class Inspircd(IProtocol): if sasl_obj.auth_success: self.__Irc.insert_db_admin(sasl_obj.client_uid, sasl_obj.username, sasl_obj.level, sasl_obj.language) self.send_priv_msg(nick_from=dnickname, - msg=tr("[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, nickname, sasl_obj.username, dnickname), + msg=tr("[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s", green, nogc, nickname, sasl_obj.username, dnickname), channel=dchanlog) self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Successfuly connected to %s", dnickname)) else: self.send_priv_msg(nick_from=dnickname, - msg=tr("[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s", RED, NOGC, nickname, sasl_obj.username), + msg=tr("[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s", red, nogc, nickname, sasl_obj.username), channel=dchanlog) self.send_notice(nick_from=dnickname, nick_to=nickname, msg=tr("Wrong password!")) @@ -805,22 +806,22 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}", exc_info=True) - def on_privmsg(self, serverMsg: list[str]) -> None: + def on_privmsg(self, server_msg: list[str]) -> None: """Handle PRIVMSG message coming from the server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ try: - srv_msg = serverMsg.copy() - cmd = serverMsg.copy() + srv_msg = server_msg.copy() + cmd = server_msg.copy() # Supprimer la premiere valeur si MTAGS activé if cmd[0].startswith('@'): cmd.pop(0) get_uid_or_nickname = str(cmd[0].replace(':','')) user_trigger = self.__Irc.User.get_nickname(get_uid_or_nickname) - dnickname = self.__Config.SERVICE_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 . @@ -828,7 +829,7 @@ class Inspircd(IProtocol): liste_des_commandes = list(hcmds.groups()) convert_to_string = ' '.join(liste_des_commandes) arg = convert_to_string.split() - arg.remove(f':{self.__Config.SERVICE_PREFIX}') + arg.remove(f":{self.__Config.SERVICE_PREFIX}") if not self.__Irc.Commands.is_command_exist(arg[0]): self.__Logs.debug(f"This command {arg[0]} is not available") self.send_notice( @@ -891,58 +892,58 @@ class Inspircd(IProtocol): except AttributeError as ae: self.__Logs.error(f"Attribute Error: {ae}") except Exception as err: - self.__Logs.error(f"General Error: {err} - {srv_msg}") + self.__Logs.error(f"General Error: {err}", exc_info=True) - def on_server_ping(self, serverMsg: list[str]) -> None: + def on_server_ping(self, server_msg: list[str]) -> None: """Send a PONG message to the server Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ try: # InspIRCd 4: # <- :3IN PING 808 # -> :808 PONG 3IN - hsid = str(serverMsg[0]).replace(':','') + hsid = str(server_msg[0]).replace(':','') self.send2socket(f":{self.__Config.SERVEUR_ID} PONG {hsid}", print_log=False) return None except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_server(self, serverMsg: list[str]) -> None: + def on_server(self, server_msg: list[str]) -> None: """_summary_ >>> [':97K', 'SINFO', 'customversion', ':'] >>> [':97K', 'SINFO', 'rawbranch', ':InspIRCd-4'] >>> [':97K', 'SINFO', 'rawversion', ':InspIRCd-4.8.0'] Args: - serverMsg (list[str]): The server message + server_msg (list[str]): The server message """ try: - param = str(serverMsg[2]) - self.__Config.HSID = self.__Settings.MAIN_SERVER_ID = str(serverMsg[0]).replace(':', '') + param = str(server_msg[2]) + self.__Config.HSID = self.__Settings.MAIN_SERVER_ID = str(server_msg[0]).replace(':', '') if param == 'rawversion': - self.__Logs.debug(f">> Server Version: {serverMsg[3].replace(':', '')}") + self.__Logs.debug(f">> Server Version: {server_msg[3].replace(':', '')}") elif param == 'rawbranch': - self.__Logs.debug(f">> Branch Version: {serverMsg[3].replace(':', '')}") + self.__Logs.debug(f">> Branch Version: {server_msg[3].replace(':', '')}") except Exception as err: self.__Logs.error(f'General Error: {err}') - def on_version(self, serverMsg: list[str]) -> None: + def on_version(self, server_msg: list[str]) -> None: """Sending Server Version to the server Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ # ['@unrealircd.org/userhost=StatServ@stats.deb.biz.st;draft/bot;bot;msgid=ehfAq3m2yjMjhgWEfi1UCS;time=2024-10-26T13:49:06.299Z', ':00BAAAAAI', 'PRIVMSG', '12ZAAAAAB', ':\x01VERSION\x01'] # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(server_msg[1])) dnickname = self.__Config.SERVICE_NICKNAME - arg = serverMsg[4].replace(':', '') + arg = server_msg[4].replace(':', '') if nickname is None: return None @@ -954,19 +955,19 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_time(self, serverMsg: list[str]) -> None: + def on_time(self, server_msg: list[str]) -> None: """Sending TIME answer to a requestor Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ # ['@unrealircd.org/userhost=StatServ@stats.deb.biz.st;draft/bot;bot;msgid=ehfAq3m2yjMjhgWEfi1UCS;time=2024-10-26T13:49:06.299Z', ':00BAAAAAI', 'PRIVMSG', '12ZAAAAAB', ':\x01TIME\x01'] # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(server_msg[1])) dnickname = self.__Config.SERVICE_NICKNAME - arg = serverMsg[4].replace(':', '') + arg = server_msg[4].replace(':', '') current_datetime = self.__Utils.get_sdatetime() if nickname is None: @@ -979,25 +980,25 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_ping(self, serverMsg: list[str]) -> None: + def on_ping(self, server_msg: list[str]) -> None: """Sending a PING answer to requestor Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ # ['@unrealircd.org/userhost=StatServ@stats.deb.biz.st;draft/bot;bot;msgid=ehfAq3m2yjMjhgWEfi1UCS;time=2024-10-26T13:49:06.299Z', ':001INC60B', 'PRIVMSG', '12ZAAAAAB', ':\x01PING', '762382207\x01'] # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(server_msg[1])) dnickname = self.__Config.SERVICE_NICKNAME - arg = serverMsg[4].replace(':', '') + arg = server_msg[4].replace(':', '') if nickname is None: return None if arg == '\x01PING': - recieved_unixtime = int(serverMsg[5].replace('\x01','')) + recieved_unixtime = int(server_msg[5].replace('\x01','')) current_unixtime = self.__Utils.get_unixtime() ping_response = current_unixtime - recieved_unixtime @@ -1012,37 +1013,38 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_version_msg(self, serverMsg: list[str]) -> None: + def on_version_msg(self, server_msg: list[str]) -> None: """Handle version coming from the server Args: - serverMsg (list[str]): Original message from the server + server_msg (list[str]): Original message from the server """ try: # ['@label=0073', ':0014E7P06', 'VERSION', 'PyDefender'] - getUser = self.__Irc.User.get_user(self.__Utils.clean_uid(serverMsg[1])) + user_obj = self.__Irc.User.get_user(self.__Utils.clean_uid(server_msg[1])) - if getUser is None: + if user_obj is None: return None 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}') + self.send2socket(f':{self.__Config.SERVICE_HOST} 351 {user_obj.nickname} {response_351}') 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') + self.send2socket(f':{self.__Config.SERVICE_HOST} 005 {user_obj.nickname} {response_005} are supported by this server') return None except Exception as err: self.__Logs.error(f"{__name__} - General Error: {err}") - def on_sasl(self, serverMsg: list[str]) -> Optional['MSasl']: + def on_sasl(self, server_msg: list[str]) -> Optional['MSasl']: """Handle SASL coming from a server Args: - serverMsg (list[str]): Original server message - psasl (Sasl): The SASL process object + server_msg (list[str]): Original server message + Returns: + Optional[MSasl]: The MSasl object """ try: # [':97K', 'ENCAP', '98K', 'SASL', '97KAAAAAF', '*', 'H', '172.18.128.1', '172.18.128.1', 'P'] @@ -1061,10 +1063,10 @@ class Inspircd(IProtocol): if not sasl_enabled: return None - sCopy = serverMsg.copy() - client_uid = sCopy[4] if len(sCopy) >= 6 else None - sasl_obj = None - sasl_message_type = sCopy[6] if len(sCopy) >= 6 else None + scopy = server_msg.copy() + client_uid = scopy[4] if len(scopy) >= 6 else None + # sasl_obj = None + sasl_message_type = scopy[6] if len(scopy) >= 6 else None psasl.insert_sasl_client(self.__Irc.Loader.Definition.MSasl(client_uid=client_uid)) sasl_obj = psasl.get_sasl_obj(client_uid) @@ -1073,22 +1075,22 @@ class Inspircd(IProtocol): match sasl_message_type: case 'H': - sasl_obj.remote_ip = str(sCopy[8]) + sasl_obj.remote_ip = str(scopy[8]) sasl_obj.message_type = sasl_message_type return sasl_obj case 'S': sasl_obj.message_type = sasl_message_type - if str(sCopy[7]) in ['PLAIN', 'EXTERNAL']: - sasl_obj.mechanisme = str(sCopy[7]) + if str(scopy[7]) in ['PLAIN', 'EXTERNAL']: + sasl_obj.mechanisme = str(scopy[7]) if sasl_obj.mechanisme == "PLAIN": self.send2socket(f":{self.__Config.SERVEUR_ID} SASL {self.__Config.SERVEUR_HOSTNAME} {sasl_obj.client_uid} C +") elif sasl_obj.mechanisme == "EXTERNAL": - if str(sCopy[7]) == "+": + if str(scopy[7]) == "+": return None - sasl_obj.fingerprint = str(sCopy[8]) + sasl_obj.fingerprint = str(scopy[8]) self.send2socket(f":{self.__Config.SERVEUR_ID} SASL {self.__Config.SERVEUR_HOSTNAME} {sasl_obj.client_uid} C +") self.on_sasl_authentication_process(sasl_obj) @@ -1096,7 +1098,7 @@ class Inspircd(IProtocol): case 'C': if sasl_obj.mechanisme == "PLAIN": - credentials = sCopy[7] + credentials = scopy[7] decoded_credentials = b64decode(credentials).decode() user, username, password = decoded_credentials.split('\0') @@ -1114,7 +1116,7 @@ class Inspircd(IProtocol): except Exception as err: self.__Logs.error(f'General Error: {err}', exc_info=True) - def on_sasl_authentication_process(self, sasl_model: 'MSasl') -> bool: + def on_sasl_authentication_process(self, sasl_model: 'MSasl'): s = sasl_model server_id = self.__Config.SERVEUR_ID main_server_hostname = self.__Settings.MAIN_SERVER_HOSTNAME @@ -1164,22 +1166,23 @@ class Inspircd(IProtocol): self.send2socket(f":{server_id} SASL {main_server_hostname} {s.client_uid} D F") self.send2socket(f":{server_id} SASL {s.username} :SASL authentication failed") - def on_error(self, serverMsg: list[str]) -> None: - self.__Logs.debug(f"{serverMsg}") + def on_error(self, server_msg: list[str]) -> None: + self.__Logs.debug(f"{server_msg}") - def on_metedata(self, serverMsg: list[str]) -> None: + def on_metedata(self, server_msg: list[str]) -> None: """_summary_ Args: - serverMsg (list[str]): _description_ + server_msg (list[str]): _description_ """ # [':97K', 'METADATA', '97KAAAAAA', 'ssl_cert', ':vTrSe', 'fingerprint90753683519522875', # '/C=FR/OU=Testing/O=Test', 'Sasl/CN=localhost', '/C=FR/OU=Testing/O=Test', 'Sasl/CN=localhost'] - scopy = serverMsg.copy() + scopy = server_msg.copy() dnickname = self.__Config.SERVICE_NICKNAME dchanlog = self.__Config.SERVICE_CHANLOG - GREEN = self.__Config.COLORS.green - NOGC = self.__Config.COLORS.nogc + green = self.__Config.COLORS.green + nogc = self.__Config.COLORS.nogc + if 'ssl_cert' in scopy: fingerprint = scopy[5] uid = scopy[2] @@ -1190,7 +1193,7 @@ class Inspircd(IProtocol): admin = self.__Irc.Admin.get_admin(uid) account = admin.account if admin else '' self.send_priv_msg(nick_from=dnickname, - msg=tr("[ %sSASL AUTO AUTH%s ] - %s (%s) is now connected successfuly to %s", GREEN, NOGC, user_obj.nickname, account, dnickname), + msg=tr("[ %sSASL AUTO AUTH%s ] - %s (%s) is now connected successfuly to %s", green, nogc, user_obj.nickname, account, dnickname), channel=dchanlog) self.send_notice(nick_from=dnickname, nick_to=user_obj.nickname, msg=tr("Successfuly connected to %s", dnickname)) @@ -1198,46 +1201,46 @@ class Inspircd(IProtocol): # COMMON IRC PARSER # ------------------------------------------------------------------------ - def parse_uid(self, serverMsg: list[str]) -> dict[str, str]: + def parse_uid(self, server_msg: list[str]) -> dict[str, str]: """Parse UID and return dictionary. Args: - serverMsg (list[str]): _description_ + server_msg (list[str]): _description_ """ - umodes = str(serverMsg[11]) - remote_ip = serverMsg[9] if 'S' not in umodes else '127.0.0.1' + umodes = str(server_msg[11]) + remote_ip = server_msg[9] if 'S' not in umodes else '127.0.0.1' # Extract Geoip information pattern = r'^.*geoip=cc=(\S{2}).*$' - geoip_match = match(pattern, serverMsg[0]) + geoip_match = match(pattern, server_msg[0]) geoip = geoip_match.group(1) if geoip_match else None response = { - 'uid': str(serverMsg[2]), - 'nickname': str(serverMsg[4]), - 'username': str(serverMsg[7]), - 'hostname': str(serverMsg[5]), + 'uid': str(server_msg[2]), + 'nickname': str(server_msg[4]), + 'username': str(server_msg[7]), + 'hostname': str(server_msg[5]), 'umodes': umodes, - 'vhost': str(serverMsg[6]), + 'vhost': str(server_msg[6]), 'ip': remote_ip, - 'realname': ' '.join(serverMsg[12:]).lstrip(':'), + 'realname': ' '.join(server_msg[12:]).lstrip(':'), 'geoip': geoip, 'reputation_score': 0, - 'iswebirc': True if 'webirc' in serverMsg[0] else False, - 'iswebsocket': True if 'websocket' in serverMsg[0] else False + 'iswebirc': True if 'webirc' in server_msg[0] else False, + 'iswebsocket': True if 'websocket' in server_msg[0] else False } return response - def parse_quit(self, serverMsg: list[str]) -> dict[str, str]: + def parse_quit(self, server_msg: list[str]) -> dict[str, str]: """Parse quit and return dictionary. >>> [':97KAAAAAB', 'QUIT', ':Quit:', 'this', 'is', 'my', 'reason', 'to', 'quit'] Args: - serverMsg (list[str]): The server message to parse + server_msg (list[str]): The server message to parse Returns: dict[str, str]: The dictionary. """ - scopy = serverMsg.copy() + scopy = server_msg.copy() if scopy[0].startswith('@'): scopy.pop(0) @@ -1248,17 +1251,17 @@ class Inspircd(IProtocol): } return response - def parse_nick(self, serverMsg: list[str]) -> dict[str, str]: + def parse_nick(self, server_msg: list[str]) -> dict[str, str]: """Parse nick changes. >>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740'] Args: - serverMsg (list[str]): The server message to parse + server_msg (list[str]): The server message to parse Returns: dict[str, str]: The response as dictionary. """ - scopy = serverMsg.copy() + scopy = server_msg.copy() if scopy[0].startswith('@'): scopy.pop(0) @@ -1269,18 +1272,18 @@ class Inspircd(IProtocol): } return response - def parse_privmsg(self, serverMsg: list[str]) -> dict[str, str]: + def parse_privmsg(self, server_msg: list[str]) -> dict[str, str]: """Parse PRIVMSG message. >>> [':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message'] >>> [':97KAAAAAF', 'PRIVMSG', '98KAAAAAB', ':My','Message','...'] Args: - serverMsg (list[str]): The server message to parse + server_msg (list[str]): The server message to parse Returns: dict[str, str]: The response as dictionary. """ - scopy = serverMsg.copy() + scopy = server_msg.copy() if scopy[0].startswith('@'): scopy.pop(0) @@ -1307,15 +1310,15 @@ class Inspircd(IProtocol): ... def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: - """_summary_ + """Send a gline command to the server Args: - nickname (str): _description_ - hostname (str): _description_ - set_by (str): _description_ - expire_timestamp (int): _description_ - set_at_timestamp (int): _description_ - reason (str): _description_ + nickname (str): The nickname of the client. + hostname (str): The hostname of the client. + set_by (str): The nickname who send the gline + expire_timestamp (int): Expire timestamp + set_at_timestamp (int): Set at timestamp + reason (str): The reason of the gline. """ ... @@ -1332,8 +1335,7 @@ class Inspircd(IProtocol): """_summary_ Args: - from_nick (str): _description_ - nick_to (str): _description_ + nick_to_sapart (str): _description_ channel_name (str): _description_ """ ... @@ -1370,7 +1372,7 @@ class Inspircd(IProtocol): """Logout a client from his account Args: - client_uid (str): The Client UID + client_obj (MClient): The Client Object Model """ ... @@ -1393,51 +1395,51 @@ class Inspircd(IProtocol): """ ... - def on_md(self, serverMsg: list[str]) -> None: + def on_md(self, server_msg: list[str]) -> None: """Handle MD responses [':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...'] Args: - serverMsg (list[str]): The server reply + server_msg (list[str]): The server reply """ ... - def on_mode(self, serverMsg: list[str]) -> None: + def on_mode(self, server_msg: list[str]) -> None: """Handle mode coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ ... - def on_reputation(self, serverMsg: list[str]) -> None: + def on_reputation(self, server_msg: list[str]) -> None: """Handle REPUTATION coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ ... - def on_smod(self, serverMsg: list[str]) -> None: + def on_smod(self, server_msg: list[str]) -> None: """Handle SMOD message coming from the server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ ... - def on_svs2mode(self, serverMsg: list[str]) -> None: + def on_svs2mode(self, server_msg: list[str]) -> None: """Handle svs2mode coming from a server >>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r'] Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ ... - def on_eos(self, serverMsg: list[str]) -> None: + def on_eos(self, server_msg: list[str]) -> None: """Handle EOS coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ ... diff --git a/core/classes/protocols/interface.py b/core/classes/protocols/interface.py index 00c2d1f..676d383 100644 --- a/core/classes/protocols/interface.py +++ b/core/classes/protocols/interface.py @@ -1,15 +1,14 @@ from abc import ABC, abstractmethod from typing import Optional, TYPE_CHECKING +from core.classes.protocols.command_handler import CommandHandler if TYPE_CHECKING: - from core.classes.sasl import Sasl - from core.definition import MClient, MSasl, MRegister - from core.classes.protocols.command_handler import CommandHandler + from core.definition import MClient, MSasl + class IProtocol(ABC): - DB_REGISTER: list['MRegister'] = [] - Handler: Optional['CommandHandler'] = None + Handler: Optional[CommandHandler] = None @abstractmethod def get_ircd_protocol_poisition(self, cmd: list[str], log: bool = False) -> tuple[int, Optional[str]]: @@ -33,7 +32,8 @@ class IProtocol(ABC): """Envoit les commandes à envoyer au serveur. Args: - string (Str): contient la commande à envoyer au serveur. + message (str): contient la commande à envoyer au serveur. + print_log (bool): If True then print logs """ @abstractmethod @@ -65,15 +65,15 @@ class IProtocol(ABC): @abstractmethod def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: - """_summary_ + """Send a gline command to the server Args: - nickname (str): _description_ - hostname (str): _description_ - set_by (str): _description_ - expire_timestamp (int): _description_ - set_at_timestamp (int): _description_ - reason (str): _description_ + nickname (str): The nickname of the client. + hostname (str): The hostname of the client. + set_by (str): The nickname who send the gline + expire_timestamp (int): Expire timestamp + set_at_timestamp (int): Set at timestamp + reason (str): The reason of the gline. """ @abstractmethod @@ -149,8 +149,7 @@ class IProtocol(ABC): """_summary_ Args: - from_nick (str): _description_ - nick_to (str): _description_ + nick_to_sapart (str): _description_ channel_name (str): _description_ """ @@ -215,18 +214,19 @@ class IProtocol(ABC): """Logout a client from his account Args: - client_uid (str): The Client UID + client_obj (MClient): The Client UID """ @abstractmethod - def send_quit(self, uid: str, reason: str, print_log: True) -> None: + def send_quit(self, uid: str, reason: str, print_log: bool = True) -> None: """Send quit message - Delete uid from User object - Delete uid from Reputation object Args: - uidornickname (str): The UID or the Nickname + uid (str): The UID or the Nickname reason (str): The reason for the quit + print_log (bool): If True then print logs """ @abstractmethod @@ -288,46 +288,46 @@ class IProtocol(ABC): # ------------------------------------------------------------------------ @abstractmethod - def parse_uid(self, serverMsg: list[str]) -> dict[str, str]: + def parse_uid(self, server_msg: list[str]) -> dict[str, str]: """Parse UID and return dictionary. Args: - serverMsg (list[str]): The UID IRCD message + server_msg (list[str]): The UID IRCD message Returns: dict[str, str]: The response as dictionary. """ @abstractmethod - def parse_quit(self, serverMsg: list[str]) -> dict[str, str]: + def parse_quit(self, server_msg: list[str]) -> dict[str, str]: """Parse quit and return dictionary. >>> [':97KAAAAAB', 'QUIT', ':Quit:', 'this', 'is', 'my', 'reason', 'to', 'quit'] Args: - serverMsg (list[str]): The server message to parse + server_msg (list[str]): The server message to parse Returns: dict[str, str]: The response as dictionary. """ @abstractmethod - def parse_nick(self, serverMsg: list[str]) -> dict[str, str]: + def parse_nick(self, server_msg: list[str]) -> dict[str, str]: """Parse nick changes and return dictionary. >>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740'] Args: - serverMsg (list[str]): The server message to parse + server_msg (list[str]): The server message to parse Returns: dict[str, str]: The response as dictionary. """ @abstractmethod - def parse_privmsg(self, serverMsg: list[str]) -> dict[str, str]: + def parse_privmsg(self, server_msg: list[str]) -> dict[str, str]: """Parse PRIVMSG message. >>> [':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message'] Args: - serverMsg (list[str]): The server message to parse + server_msg (list[str]): The server message to parse Returns: dict[str, str]: The response as dictionary. @@ -345,175 +345,177 @@ class IProtocol(ABC): # ------------------------------------------------------------------------ @abstractmethod - def on_svs2mode(self, serverMsg: list[str]) -> None: + def on_svs2mode(self, server_msg: list[str]) -> None: """Handle svs2mode coming from a server >>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r'] Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_mode(self, serverMsg: list[str]) -> None: + def on_mode(self, server_msg: list[str]) -> None: """Handle mode coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_umode2(self, serverMsg: list[str]) -> None: + def on_umode2(self, server_msg: list[str]) -> None: """Handle umode2 coming from a server >>> [':adator_', 'UMODE2', '-i'] Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_quit(self, serverMsg: list[str]) -> None: + def on_quit(self, server_msg: list[str]) -> None: """Handle quit coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_squit(self, serverMsg: list[str]) -> None: + def on_squit(self, server_msg: list[str]) -> None: """Handle squit coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_protoctl(self, serverMsg: list[str]) -> None: + def on_protoctl(self, server_msg: list[str]) -> None: """Handle protoctl coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_nick(self, serverMsg: list[str]) -> None: + def on_nick(self, server_msg: list[str]) -> None: """Handle nick coming from a server new nickname Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_sjoin(self, serverMsg: list[str]) -> None: + def on_sjoin(self, server_msg: list[str]) -> None: """Handle sjoin coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_part(self, serverMsg: list[str]) -> None: + def on_part(self, server_msg: list[str]) -> None: """Handle part coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_eos(self, serverMsg: list[str]) -> None: + def on_eos(self, server_msg: list[str]) -> None: """Handle EOS coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_reputation(self, serverMsg: list[str]) -> None: + def on_reputation(self, server_msg: list[str]) -> None: """Handle REPUTATION coming from a server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_uid(self, serverMsg: list[str]) -> None: + def on_uid(self, server_msg: list[str]) -> None: """Handle uid message coming from the server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_privmsg(self, serverMsg: list[str]) -> None: + def on_privmsg(self, server_msg: list[str]) -> None: """Handle PRIVMSG message coming from the server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_server_ping(self, serverMsg: list[str]) -> None: + def on_server_ping(self, server_msg: list[str]) -> None: """Send a PONG message to the server Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ @abstractmethod - def on_server(self, serverMsg: list[str]) -> None: + def on_server(self, server_msg: list[str]) -> None: """_summary_ Args: - serverMsg (list[str]): _description_ + server_msg (list[str]): _description_ """ @abstractmethod - def on_version(self, serverMsg: list[str]) -> None: + def on_version(self, server_msg: list[str]) -> None: """Sending Server Version to the server Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ @abstractmethod - def on_time(self, serverMsg: list[str]) -> None: + def on_time(self, server_msg: list[str]) -> None: """Sending TIME answer to a requestor Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ @abstractmethod - def on_ping(self, serverMsg: list[str]) -> None: + def on_ping(self, server_msg: list[str]) -> None: """Sending a PING answer to requestor Args: - serverMsg (list[str]): List of str coming from the server + server_msg (list[str]): List of str coming from the server """ @abstractmethod - def on_version_msg(self, serverMsg: list[str]) -> None: + def on_version_msg(self, server_msg: list[str]) -> None: """Handle version coming from the server \n ex. /version Defender Args: - serverMsg (list[str]): Original message from the server + server_msg (list[str]): Original message from the server """ @abstractmethod - def on_smod(self, serverMsg: list[str]) -> None: + def on_smod(self, server_msg: list[str]) -> None: """Handle SMOD message coming from the server Args: - serverMsg (list[str]): Original server message + server_msg (list[str]): Original server message """ @abstractmethod - def on_sasl(self, serverMsg: list[str], psasl: 'Sasl') -> Optional['MSasl']: + def on_sasl(self, server_msg: list[str]) -> Optional['MSasl']: """Handle SASL coming from a server Args: - serverMsg (list[str]): Original server message - psasl (Sasl): The SASL process object + server_msg (list[str]): Original server message + + Returns: + """ @abstractmethod @@ -528,9 +530,9 @@ class IProtocol(ABC): """ @abstractmethod - def on_md(self, serverMsg: list[str]) -> None: + def on_md(self, server_msg: list[str]) -> None: """Handle MD responses [':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...'] Args: - serverMsg (list[str]): The server reply + server_msg (list[str]): The server reply """ diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index 8e10746..0c94362 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -202,43 +202,52 @@ class Unrealircd6(IProtocol): """Créer le link et envoyer les informations nécessaires pour la connexion au serveur. """ - - nickname = self.__Config.SERVICE_NICKNAME - username = self.__Config.SERVICE_USERNAME - realname = self.__Config.SERVICE_REALNAME - chan = self.__Config.SERVICE_CHANLOG - info = self.__Config.SERVICE_INFO - smodes = self.__Config.SERVICE_SMODES - cmodes = self.__Config.SERVICE_CMODES - umodes = self.__Config.SERVICE_UMODES - host = self.__Config.SERVICE_HOST + service_id = self.__Config.SERVICE_ID + service_nickname = self.__Config.SERVICE_NICKNAME + service_username = self.__Config.SERVICE_USERNAME + service_realname = self.__Config.SERVICE_REALNAME + service_channel_log = self.__Config.SERVICE_CHANLOG + service_info = self.__Config.SERVICE_INFO + service_smodes = self.__Config.SERVICE_SMODES + service_cmodes = self.__Config.SERVICE_CMODES + service_umodes = self.__Config.SERVICE_UMODES + service_hostname = self.__Config.SERVICE_HOST service_name = self.__Config.SERVICE_NAME protocolversion = self.protocol_version - password = self.__Config.SERVEUR_PASSWORD - link = self.__Config.SERVEUR_LINK + server_password = self.__Config.SERVEUR_PASSWORD + server_link = self.__Config.SERVEUR_LINK server_id = self.__Config.SERVEUR_ID - service_id = self.__Config.SERVICE_ID version = self.__Config.CURRENT_VERSION unixtime = self.__Utils.get_unixtime() - self.send2socket(f":{server_id} PASS :{password}", print_log=False) + self.send2socket(f":{server_id} PASS :{server_password}", print_log=False) self.send2socket(f":{server_id} PROTOCTL SID NOQUIT NICKv2 SJOIN SJ3 NICKIP TKLEXT2 NEXTBANS CLK EXTSWHOIS MLOCK MTAGS") - self.send2socket(f":{server_id} PROTOCTL EAUTH={link},{protocolversion},,{service_name}-v{version}") + self.send2socket(f":{server_id} PROTOCTL EAUTH={server_link},{protocolversion},,{service_name}-v{version}") self.send2socket(f":{server_id} PROTOCTL SID={server_id}") self.send2socket(f":{server_id} PROTOCTL BOOTED={unixtime}") - self.send2socket(f":{server_id} SERVER {link} 1 :{info}") + self.send2socket(f":{server_id} SERVER {server_link} 1 :{service_info}") self.send2socket("EOS") - self.send2socket(f":{server_id} {nickname} :Reserved for services") - self.send2socket(f":{server_id} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * fwAAAQ== :{realname}") - self.send_sjoin(chan) - self.send2socket(f":{server_id} TKL + Q * {nickname} {host} 0 {unixtime} :Reserved for services") - self.send2socket(f":{service_id} MODE {chan} {cmodes}") + self.send2socket(f":{server_id} {service_nickname} :Reserved for services") + self.send2socket(f":{server_id} UID {service_nickname} 1 {unixtime} {service_username} {service_hostname} {service_id} * {service_smodes} * * fwAAAQ== :{service_realname}") + self.send_sjoin(service_channel_log) + self.send2socket(f":{server_id} TKL + Q * {service_nickname} {service_hostname} 0 {unixtime} :Reserved for services") + self.send2socket(f":{service_id} MODE {service_channel_log} {service_cmodes}") self.__Logs.debug(f'>> {__name__} Link information sent to the server') def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + """Send a gline command to the server + + Args: + nickname (str): The nickname of the client. + hostname (str): The hostname of the client. + set_by (str): The nickname who send the gline + expire_timestamp (int): Expire timestamp + set_at_timestamp (int): Set at timestamp + reason (str): The reason of the gline. + """ # TKL + G user host set_by expire_timestamp set_at_timestamp :reason self.send2socket(f":{self.__Config.SERVEUR_ID} TKL + G {nickname} {hostname} {set_by} {expire_timestamp} {set_at_timestamp} :{reason}") @@ -1286,7 +1295,7 @@ class Unrealircd6(IProtocol): except AttributeError as ae: self.__Logs.error(f"Attribute Error: {ae}") except Exception as err: - self.__Logs.error(f"General Error: {err} - {srv_msg}") + self.__Logs.error(f"General Error: {err} - {srv_msg}" , exc_info=True) def on_server_ping(self, serverMsg: list[str]) -> None: """Send a PONG message to the server diff --git a/core/classes/rehash.py b/core/classes/rehash.py index 3f85ed3..da8cd61 100644 --- a/core/classes/rehash.py +++ b/core/classes/rehash.py @@ -22,7 +22,12 @@ REHASH_MODULES = [ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -> None: + """ + Args: + uplink (Irc): The Irc instance + reason (str): The reason of the restart. + """ # reload modules. for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): uplink.ModuleUtils.unload_one_module(uplink, module.module_name) @@ -34,7 +39,7 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") - uplink.Base.garbage_collector_thread() 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.Protocol.send_squit(server_id=uplink.Config.SERVEUR_ID, server_link=uplink.Config.SERVEUR_LINK, reason=reason) uplink.Logs.debug('Restarting Defender ...') uplink.IrcSocket.shutdown(socket.SHUT_RDWR) uplink.IrcSocket.close() @@ -72,13 +77,13 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None: channel=uplink.Config.SERVICE_CHANLOG ) uplink.Utils = sys.modules['core.utils'] - uplink.Loader.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model() - uplink.Loader.Config.HSID = config_model_bakcup.HSID - uplink.Loader.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT - uplink.Loader.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART - uplink.Loader.Config.SSL_VERSION = config_model_bakcup.SSL_VERSION - uplink.Loader.Config.CURRENT_VERSION = config_model_bakcup.CURRENT_VERSION - uplink.Loader.Config.LATEST_VERSION = config_model_bakcup.LATEST_VERSION + 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() diff --git a/core/classes/reputation.py b/core/classes/reputation.py index 3ee0d47..242290a 100644 --- a/core/classes/reputation.py +++ b/core/classes/reputation.py @@ -9,9 +9,14 @@ class Reputation: UID_REPUTATION_DB: list[MReputation] = [] def __init__(self, loader: 'Loader'): + """ + + Args: + loader (Loader): The Loader instance. + """ self.Logs = loader.Logs - self.MReputation: MReputation = MReputation + self.MReputation: Optional[MReputation] = None def insert(self, new_reputation_user: MReputation) -> bool: """Insert a new Reputation User object @@ -47,7 +52,7 @@ class Reputation: Args: uid (str): UID of the user - newNickname (str): New nickname + new_nickname (str): New nickname Returns: bool: True if updated diff --git a/core/classes/sasl.py b/core/classes/sasl.py index 8595cd9..a249a06 100644 --- a/core/classes/sasl.py +++ b/core/classes/sasl.py @@ -1,4 +1,4 @@ -from typing import Optional, Union, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from core.definition import MSasl @@ -9,13 +9,18 @@ class Sasl: DB_SASL: list['MSasl'] = [] def __init__(self, loader: 'Loader'): + """ + + Args: + loader (Loader): The Loader instance. + """ self.Logs = loader.Logs # logger def insert_sasl_client(self, psasl: 'MSasl') -> bool: """Insert a new Sasl authentication Args: - new_user (UserModel): New userModel object + psasl (MSasl): New userModel object Returns: bool: True if inserted @@ -38,7 +43,7 @@ class Sasl: """Delete the User starting from the UID Args: - uid (str): UID of the user + client_uid (str): UID of the user Returns: bool: True if deleted diff --git a/core/classes/settings.py b/core/classes/settings.py index fa503aa..913fbbc 100644 --- a/core/classes/settings.py +++ b/core/classes/settings.py @@ -1,5 +1,5 @@ -'''This class should never be reloaded. -''' +"""This class should never be reloaded. +""" from logging import Logger from threading import Timer, Thread, RLock from socket import socket @@ -18,7 +18,7 @@ class Settings: RUNNING_TIMERS: list[Timer] = [] RUNNING_THREADS: list[Thread] = [] RUNNING_SOCKETS: list[socket] = [] - PERIODIC_FUNC: dict[object] = {} + PERIODIC_FUNC: dict[str, Any] = {} LOCK: RLock = RLock() CONSOLE: bool = False diff --git a/core/classes/translation.py b/core/classes/translation.py index 19849ea..6e0bad7 100644 --- a/core/classes/translation.py +++ b/core/classes/translation.py @@ -11,9 +11,13 @@ if TYPE_CHECKING: class Translation: def __init__(self, loader: 'Loader') -> None: + """ + + Args: + loader (Loader): The Loader instance. + """ self.Logs = loader.Logs self.Settings = loader.Settings - return None def get_translation(self) -> dict[str, list[list[str]]]: try: diff --git a/core/irc.py b/core/irc.py index 3239c1b..08b4263 100644 --- a/core/irc.py +++ b/core/irc.py @@ -1014,7 +1014,7 @@ class Irc: except KeyError as ke: self.Logs.error(f"Key Error: {ke} - list recieved: {cmd}") except Exception as err: - self.Logs.error(f"General Error: {ke} - list recieved: {cmd}") + self.Logs.error(f"General Error: {err} - list recieved: {cmd}", exc_info=True) case 'unload': # unload mod_defender diff --git a/core/module.py b/core/module.py index 97438fa..e4317e4 100644 --- a/core/module.py +++ b/core/module.py @@ -206,6 +206,7 @@ class Module: 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!") + self.db_delete_module(module_name) 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!", diff --git a/mods/jsonrpc/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py index 9cb324e..1b7bcea 100644 --- a/mods/jsonrpc/mod_jsonrpc.py +++ b/mods/jsonrpc/mod_jsonrpc.py @@ -1,13 +1,13 @@ import logging import asyncio +from unrealircd_rpc_py.objects.Definition import LiveRPCResult import mods.jsonrpc.utils as utils import mods.jsonrpc.threads as thds from time import sleep -from types import SimpleNamespace from typing import TYPE_CHECKING from dataclasses import dataclass -from unrealircd_rpc_py.Live import LiveWebsocket, LiveUnixSocket -from unrealircd_rpc_py.Loader import Loader +from unrealircd_rpc_py.ConnectionFactory import ConnectionFactory +from unrealircd_rpc_py.LiveConnectionFactory import LiveConnectionFactory if TYPE_CHECKING: from core.irc import Irc @@ -85,43 +85,24 @@ class Jsonrpc(): self.__load_module_configuration() # End of mandatory methods you can start your customization # - self.UnrealIrcdRpcLive: LiveWebsocket = LiveWebsocket( - url=self.Config.JSONRPC_URL, - username=self.Config.JSONRPC_USER, - password=self.Config.JSONRPC_PASSWORD, - callback_object_instance=self, - callback_method_or_function_name='callback_sent_to_irc' - ) - - if self.UnrealIrcdRpcLive.get_error.code != 0: - self.Logs.error(f"{self.UnrealIrcdRpcLive.get_error.message} ({self.UnrealIrcdRpcLive.get_error.code})") + try: + self.Rpc = ConnectionFactory(self.Config.DEBUG_LEVEL).get(self.Config.JSONRPC_METHOD) + self.LiveRpc = LiveConnectionFactory(self.Config.DEBUG_LEVEL).get(self.Config.JSONRPC_METHOD) + self.Rpc.setup({'url': self.Config.JSONRPC_URL, 'username': self.Config.JSONRPC_USER, 'password': self.Config.JSONRPC_PASSWORD}) + self.LiveRpc.setup({'url': self.Config.JSONRPC_URL, 'username': self.Config.JSONRPC_USER, 'password': self.Config.JSONRPC_PASSWORD, + 'callback_object_instance' : self, 'callback_method_or_function_name': 'callback_sent_to_irc'}) + + if self.ModConfig.jsonrpc == 1: + self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True) + + return None + except Exception as err: self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}", + msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {err.__str__()}", channel=self.Config.SERVICE_CHANLOG - ) - raise Exception(f"[LIVE-JSONRPC ERROR] {self.UnrealIrcdRpcLive.get_error.message}") - - self.Rpc: Loader = Loader( - req_method=self.Config.JSONRPC_METHOD, - url=self.Config.JSONRPC_URL, - username=self.Config.JSONRPC_USER, - password=self.Config.JSONRPC_PASSWORD - ) - - if self.Rpc.get_error.code != 0: - self.Logs.error(f"{self.Rpc.get_error.message} ({self.Rpc.get_error.code})") - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}", - channel=self.Config.SERVICE_CHANLOG - ) - raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}") - - if self.ModConfig.jsonrpc == 1: - self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True) - - return None + ) + self.Logs.error(f"JSONRPC ERROR: {err.__str__()}") def __create_tables(self) -> None: """Methode qui va créer la base de donnée si elle n'existe pas. @@ -143,7 +124,7 @@ class Jsonrpc(): self.Base.db_execute_query(table_logs) return None - def callback_sent_to_irc(self, response: SimpleNamespace) -> None: + def callback_sent_to_irc(self, response: LiveRPCResult) -> None: dnickname = self.Config.SERVICE_NICKNAME dchanlog = self.Config.SERVICE_CHANLOG @@ -152,29 +133,19 @@ class Jsonrpc(): bold = self.Config.COLORS.bold red = self.Config.COLORS.red - if self.UnrealIrcdRpcLive.get_error.code != 0: + if response.error.code != 0: self.Protocol.send_priv_msg(nick_from=dnickname, - msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {self.UnrealIrcdRpcLive.get_error.message}", + msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {response.error.message} ({response.error.code})", channel=dchanlog) return None - if hasattr(response, 'error'): - if response.error.code != 0: + if isinstance(response.result, bool): + if response.result: self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{bold}{red}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", - channel=dchanlog) - - return None - - if hasattr(response, 'result'): - if isinstance(response.result, bool): - if response.result: - self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", channel=dchanlog) - return None + return None level = response.result.level if hasattr(response.result, 'level') else '' subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else '' @@ -278,18 +249,13 @@ class Jsonrpc(): match option: case 'get': nickname = str(cmd[2]) - uid_to_get = self.User.get_uid(nickname) - if uid_to_get is None: - return None - rpc = self.Rpc - UserInfo = rpc.User.get(uid_to_get) - if rpc.get_error.code != 0: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.get_error.message}') + UserInfo = rpc.User.get(nickname) + if UserInfo.error.code != 0: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{UserInfo.error.message}') return None - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'UID : {UserInfo.id}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'NICKNAME : {UserInfo.name}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}') @@ -321,9 +287,8 @@ class Jsonrpc(): case 'jrinstances': try: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.MainUtils.run_python_garbage_collector()}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveWebsocket)}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.MainUtils.get_number_gc_objects(LiveUnixSocket)}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.MainUtils.get_number_gc_objects(Loader)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveConnectionFactory)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance ConnectionFactory: {self.MainUtils.get_number_gc_objects(ConnectionFactory)}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.MainUtils.get_number_gc_objects()}") except Exception as err: self.Logs.error(f"Unknown Error: {err}") \ No newline at end of file diff --git a/mods/jsonrpc/threads.py b/mods/jsonrpc/threads.py index f35b902..1585acd 100644 --- a/mods/jsonrpc/threads.py +++ b/mods/jsonrpc/threads.py @@ -5,24 +5,20 @@ if TYPE_CHECKING: from mods.jsonrpc.mod_jsonrpc import Jsonrpc def thread_subscribe(uplink: 'Jsonrpc') -> None: - response: dict[str, dict] = {} + snickname = uplink.Config.SERVICE_NICKNAME schannel = uplink.Config.SERVICE_CHANLOG + uplink.is_streaming = True + response = asyncio.run(uplink.LiveRpc.subscribe(["all"])) - if uplink.UnrealIrcdRpcLive.get_error.code == 0: - uplink.is_streaming = True - response = asyncio.run(uplink.UnrealIrcdRpcLive.subscribe(["all"])) - else: + if response.error.code != 0: uplink.Protocol.send_priv_msg(nick_from=snickname, - msg=f"[{uplink.Config.COLORS.red}JSONRPC ERROR{uplink.Config.COLORS.nogc}] {uplink.UnrealIrcdRpcLive.get_error.message}", + msg=f"[{uplink.Config.COLORS.red}JSONRPC ERROR{uplink.Config.COLORS.nogc}] {response.error.message}", channel=schannel ) - if response is None: - return - - code = response.get('error', {}).get('code', 0) - message = response.get('error', {}).get('message', None) + code = response.error.code + message = response.error.message if code == 0: uplink.Protocol.send_priv_msg( @@ -39,18 +35,15 @@ def thread_subscribe(uplink: 'Jsonrpc') -> None: def thread_unsubscribe(uplink: 'Jsonrpc') -> None: - response: dict[str, dict] = asyncio.run(uplink.UnrealIrcdRpcLive.unsubscribe()) + response = asyncio.run(uplink.LiveRpc.unsubscribe()) uplink.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!") uplink.is_streaming = False uplink.update_configuration('jsonrpc', 0) snickname = uplink.Config.SERVICE_NICKNAME schannel = uplink.Config.SERVICE_CHANLOG - if response is None: - return None - - code = response.get('error', {}).get('code', 0) - message = response.get('error', {}).get('message', None) + code = response.error.code + message = response.error.message if code != 0: uplink.Protocol.send_priv_msg( From beec16f39d2ab9cae895ffe9aecf84840dab3f6b Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:49:39 +0200 Subject: [PATCH 5/6] Update to the 3.0.0 V --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index c641feb..29c414d 100644 --- a/version.json +++ b/version.json @@ -3,7 +3,7 @@ "requests": "2.32.3", "psutil": "6.0.0", - "unrealircd_rpc_py": "2.0.5", + "unrealircd_rpc_py": "3.0.0", "sqlalchemy": "2.0.35", "faker": "30.1.0", "pyyaml": "6.0.2" From 5cd82a174dfb546437d55cc477cc1d1960ca37b4 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:53:35 +0200 Subject: [PATCH 6/6] remove .vscode/ folder --- .gitignore | 1 + .vscode/settings.json | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 7977a52..56e4f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .pyenv/ +.vscode/ .venv/ .idea/ db/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d49077b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "editor.fontFamily": "Fira Code", - "editor.fontSize": 14, - "editor.cursorStyle": "block" -} \ No newline at end of file