16 Commits
V6.4.0 ... main

Author SHA1 Message Date
adator
3002313697 Fix typo in French translation for password message 2025-12-17 20:52:11 +01:00
adator
735553549f Fix punctuation in French translations 2025-12-17 20:51:42 +01:00
dktmb
4d4d0db102 update fr translate 2025-12-16 13:07:03 +01:00
adator
6f6e6d6c22 Asyncio (#105)
* Remove all related to nickserv scope

* Remove Client from the loader (nickserv like)

* Fix issues coming from the feedback of dktmb

* Update configuration file template
2025-12-16 01:20:45 +01:00
adator
91210f96ec Asyncio (#104)
* Remove all related to nickserv scope

* Remove Client from the loader (nickserv like)

* Fix issues coming from the feedback of dktmb
2025-12-16 01:00:36 +01:00
adator
54ea946da0 Merge pull request #103 from adator85/asyncio
Fix User call.
2025-12-14 21:47:18 +01:00
adator
26364f854a Fix User call. 2025-12-14 21:46:42 +01:00
adator
bfd434eca4 Merge pull request #102 from adator85/asyncio
fix dktmb issues.
2025-12-14 21:23:10 +01:00
adator
1d7a31ec20 Remove print statment on irc.py 2025-12-14 21:22:52 +01:00
adator
f2323f7a3c fix dktmb issues. 2025-12-14 21:21:21 +01:00
adator
fe96d2e906 Merge pull request #101 from adator85/asyncio
fix some issues linked to asyncio again.
2025-12-14 13:36:46 +01:00
adator
6c797b8efb fix some issues linked to asyncio again. 2025-12-14 13:35:20 +01:00
adator
8bc417abf9 Merge pull request #100 from adator85/asyncio
Updating Version to 6.4.1
2025-11-24 01:56:33 +01:00
adator
9cd338ecf8 Updating Version to 6.4.1 2025-11-24 01:56:01 +01:00
adator
ab4df40a4c Merge pull request #99 from adator85/asyncio
Fix rehash command.
2025-11-24 01:54:40 +01:00
adator
d989dcd762 Fix rehash command. adding security to force quite defender when a asyncio is blocking the system. 2025-11-24 01:52:25 +01:00
28 changed files with 365 additions and 657 deletions

View File

@@ -23,9 +23,28 @@ configuration:
OWNER: "TON_NICK_NAME" OWNER: "TON_NICK_NAME"
PASSWORD: "TON_PASSWORD" PASSWORD: "TON_PASSWORD"
JSONRPC_URL: "https://your.domaine.com:8600/api" ##########################################
JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket" # Defender JSON-RPC Configuration #
##########################################
RPC_HOST: "0.0.0.0"
RPC_PORT: 5000
RPC_USERS:
- USERNAME: "RPC_USER_1"
PASSWORD: "RPC_USER_1_PASSWORD"
- USERNAME: "RPC_USER_2"
PASSWORD: "RPC_USER_2_PASSWORD"
##########################################
# UnrealIRCD JSON-RPC Configuration #
##########################################
# unixsocket or http
JSONRPC_METHOD: "unixsocket" JSONRPC_METHOD: "unixsocket"
# If the method is unixsocket you don't need URL, USER or PASSWORD
JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket"
# If METHOD is http
JSONRPC_URL: "https://your.domaine.com:8600/api"
JSONRPC_USER: "YOUR_RPC_USER" JSONRPC_USER: "YOUR_RPC_USER"
JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD" JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD"

View File

@@ -378,7 +378,7 @@ class Base:
if run_once: if run_once:
for task in asyncio.all_tasks(): for task in asyncio.all_tasks():
if task.get_name().lower() == async_name.lower(): if task.get_name().lower() == name.lower():
return None return None
task = asyncio.create_task(func, name=name) task = asyncio.create_task(func, name=name)
@@ -431,23 +431,27 @@ class Base:
self.running_iothreads.remove(id_obj) self.running_iothreads.remove(id_obj)
return result return result
def asynctask_done(self, task: Union[asyncio.Task, asyncio.Future]): def asynctask_done(self, task: Union[asyncio.Task, asyncio.Future], context: Optional[dict[str, Any]] = None):
"""Log task when done """Log task when done
Args: Args:
task (asyncio.Task): The Asyncio Task callback task (asyncio.Task): The Asyncio Task callback
""" """
name = task.get_name() if isinstance(task, asyncio.Task) else "Thread"
task_or_future = "Task" if isinstance(task, asyncio.Task) else "Future" if context:
print(context)
task_name = "Future" if isinstance(task, asyncio.Future) else task.get_name()
task_or_future = "Task"
try: try:
if task.exception(): if task.exception():
self.logs.error(f"[ASYNCIO] {task_or_future} {name} failed with exception: {task.exception()}") self.logs.error(f"[ASYNCIO] {task_or_future} {task_name} failed with exception: {task.exception()}")
else: else:
self.logs.debug(f"[ASYNCIO] {task_or_future} {name} completed successfully.") self.logs.debug(f"[ASYNCIO] {task_or_future} {task_name} completed successfully.")
except asyncio.CancelledError as ce: except asyncio.CancelledError as ce:
self.logs.debug(f"[ASYNCIO] {task_or_future} {name} terminated with cancelled error. {ce}") self.logs.debug(f"[ASYNCIO] {task_or_future} {task_name} terminated with cancelled error. {ce}")
except asyncio.InvalidStateError as ie: except asyncio.InvalidStateError as ie:
self.logs.debug(f"[ASYNCIO] {task_or_future} {name} terminated with invalid state error. {ie}") self.logs.debug(f"[ASYNCIO] {task_or_future} {task_name} terminated with invalid state error. {ie}")
def is_thread_alive(self, thread_name: str) -> bool: def is_thread_alive(self, thread_name: str) -> bool:
"""Check if the thread is still running! using the is_alive method of Threads. """Check if the thread is still running! using the is_alive method of Threads.
@@ -611,25 +615,10 @@ class Base:
) )
''' '''
table_core_client = f'''CREATE TABLE IF NOT EXISTS {self.Config.TABLE_CLIENT} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
createdOn TEXT,
account TEXT,
nickname TEXT,
hostname TEXT,
vhost TEXT,
realname TEXT,
email TEXT,
password TEXT,
level INTEGER
)
'''
await self.db_execute_query(table_core_log) await self.db_execute_query(table_core_log)
await self.db_execute_query(table_core_log_command) await self.db_execute_query(table_core_log_command)
await self.db_execute_query(table_core_module) await self.db_execute_query(table_core_module)
await self.db_execute_query(table_core_admin) await self.db_execute_query(table_core_admin)
await self.db_execute_query(table_core_client)
await self.db_execute_query(table_core_channel) await self.db_execute_query(table_core_channel)
await self.db_execute_query(table_core_config) await self.db_execute_query(table_core_config)

View File

@@ -46,7 +46,7 @@ class IModule(ABC):
""" """
@abstractmethod @abstractmethod
def create_tables(self) -> None: async def create_tables(self) -> None:
"""Method that will create the database if it does not exist. """Method that will create the database if it does not exist.
A single Session for this class will be created, which will be used within this class/module. A single Session for this class will be created, which will be used within this class/module.

View File

@@ -3,7 +3,7 @@ from typing import Optional, TYPE_CHECKING
from core.classes.protocols.command_handler import CommandHandler from core.classes.protocols.command_handler import CommandHandler
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MClient, MSasl, MUser, MChannel from core.definition import MSasl, MUser, MChannel
from core.loader import Loader from core.loader import Loader
class IProtocol(ABC): class IProtocol(ABC):
@@ -224,11 +224,8 @@ class IProtocol(ABC):
""" """
@abstractmethod @abstractmethod
async def send_svslogout(self, client_obj: 'MClient') -> None: async def send_svslogout(self) -> None:
"""Logout a client from his account """Logout a client from his account
Args:
client_obj (MClient): The Client UID
""" """
@abstractmethod @abstractmethod
@@ -244,7 +241,7 @@ class IProtocol(ABC):
""" """
@abstractmethod @abstractmethod
async 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: async def send_uid(self, nickname:str, username: str, hostname: str, uid:str, umodes: str, vhost: str, remote_ip: str, realname: str, geoip: str, print_log: bool = True) -> None:
"""Send UID to the server """Send UID to the server
- Insert User to User Object - Insert User to User Object
Args: Args:
@@ -256,6 +253,7 @@ class IProtocol(ABC):
vhost (str): vhost 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 remote_ip (str): remote_ip of the client you want to create
realname (str): realname of the client you want to create realname (str): realname of the client you want to create
geoip (str): The country code of the client you want to create
print_log (bool, optional): print logs if true. Defaults to True. print_log (bool, optional): print logs if true. Defaults to True.
""" """

View File

@@ -67,15 +67,14 @@ class Admin:
Returns: Returns:
bool: True if the admin level has been updated bool: True if the admin level has been updated
""" """
admin_obj = self.get_admin(nickname)
for record in self.UID_ADMIN_DB: if admin_obj:
if record.nickname == nickname:
# If the admin exist, update and do not go further # If the admin exist, update and do not go further
record.level = new_admin_level admin_obj.level = new_admin_level
self._ctx.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}') self._ctx.Logs.debug(f'Admin ({admin_obj.nickname}) has been updated with new level {new_admin_level}')
return True return True
self._ctx.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin') self._ctx.Logs.debug(f'The new level {new_admin_level} was not updated in local variable, nickname = {nickname} is not logged in')
return False return False
@@ -94,7 +93,7 @@ class Admin:
self._ctx.Logs.debug(f'UID ({admin_obj.uid}) has been deleted') self._ctx.Logs.debug(f'UID ({admin_obj.uid}) has been deleted')
return True return True
self._ctx.Logs.debug(f'The UID {uidornickname} was not deleted') self._ctx.Logs.debug(f'The UID {uidornickname} was not deleted from the local variable (admin not connected)')
return False return False

View File

@@ -1,226 +0,0 @@
from re import sub
from typing import Any, Optional, Union, TYPE_CHECKING
if TYPE_CHECKING:
from core.loader import Loader
from core.definition import MClient
class Client:
CLIENT_DB: list['MClient'] = []
def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader instance.
"""
self._ctx = loader
def insert(self, new_client: 'MClient') -> bool:
"""Insert a new User object
Args:
new_client (MClient): New Client object
Returns:
bool: True if inserted
"""
client_obj = self.get_client(new_client.uid)
if not client_obj is None:
# User already created return False
return False
self.CLIENT_DB.append(new_client)
return True
def update_nickname(self, uid: str, new_nickname: str) -> bool:
"""Update the nickname starting from the UID
Args:
uid (str): UID of the user
new_nickname (str): New nickname
Returns:
bool: True if updated
"""
user_obj = self.get_client(uidornickname=uid)
if user_obj is None:
return False
user_obj.nickname = new_nickname
return True
def update_mode(self, uidornickname: str, modes: str) -> bool:
"""Updating user mode
Args:
uidornickname (str): The UID or Nickname of the user
modes (str): new modes to update
Returns:
bool: True if user mode has been updaed
"""
response = True
user_obj = self.get_client(uidornickname=uidornickname)
if user_obj is None:
return False
action = modes[0]
new_modes = modes[1:]
existing_umodes = user_obj.umodes
umodes = user_obj.umodes
if action == '+':
for nm in new_modes:
if nm not in existing_umodes:
umodes += nm
elif action == '-':
for nm in new_modes:
if nm in existing_umodes:
umodes = umodes.replace(nm, '')
else:
return False
liste_umodes = list(umodes)
final_umodes_liste = [x for x in self._ctx.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes = ''.join(final_umodes_liste)
user_obj.umodes = f"+{final_umodes}"
return response
def delete(self, uid: str) -> bool:
"""Delete the User starting from the UID
Args:
uid (str): UID of the user
Returns:
bool: True if deleted
"""
user_obj = self.get_client(uidornickname=uid)
if user_obj is None:
return False
self.CLIENT_DB.remove(user_obj)
return True
def get_client(self, uidornickname: str) -> Optional['MClient']:
"""Get The Client Object model
Args:
uidornickname (str): UID or Nickname
Returns:
UserModel|None: The UserModel Object | None
"""
for record in self.CLIENT_DB:
if record.uid == uidornickname:
return record
elif record.nickname == uidornickname:
return record
return None
def get_uid(self, uidornickname:str) -> Optional[str]:
"""Get the UID of the user starting from the UID or the Nickname
Args:
uidornickname (str): UID or Nickname
Returns:
str|None: Return the UID
"""
client_obj = self.get_client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.uid
def get_nickname(self, uidornickname:str) -> Union[str, None]:
"""Get the Nickname starting from UID or the nickname
Args:
uidornickname (str): UID or Nickname of the user
Returns:
str|None: the nickname
"""
client_obj = self.get_client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.nickname
def is_exist(self, uidornickname: str) -> bool:
"""Check if the UID or the nickname exist in the USER DB
Args:
uidornickname (str): The UID or the NICKNAME
Returns:
bool: True if exist
"""
user_obj = self.get_client(uidornickname=uidornickname)
if user_obj is None:
return False
return True
async def db_is_account_exist(self, account: str) -> bool:
"""Check if the account exist in the database
Args:
account (str): The account to check
Returns:
bool: True if exist
"""
table_client = self._ctx.Base.Config.TABLE_CLIENT
account_to_check = {'account': account.lower()}
account_to_check_query = await self._ctx.Base.db_execute_query(f"""
SELECT id FROM {table_client} WHERE LOWER(account) = :account
""", account_to_check)
account_to_check_result = account_to_check_query.fetchone()
if account_to_check_result:
self._ctx.Logs.error(f"Account ({account}) already exist")
return True
return False
def clean_uid(self, uid: str) -> Union[str, None]:
"""Clean UID by removing @ / % / + / ~ / * / :
Args:
uid (str): The UID to clean
Returns:
str: Clean UID without any sign
"""
pattern = fr'[:|@|%|\+|~|\*]*'
parsed_uid = sub(pattern, '', uid)
if not parsed_uid:
return None
return parsed_uid

View File

@@ -1,9 +1,11 @@
import asyncio import asyncio
import importlib import importlib
import sys import sys
import time import threading
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import socket import core.module as module_mod
from core.classes.modules import user, admin, channel, reputation, sasl
from core.utils import tr
if TYPE_CHECKING: if TYPE_CHECKING:
from core.loader import Loader from core.loader import Loader
@@ -12,9 +14,15 @@ if TYPE_CHECKING:
REHASH_MODULES = [ REHASH_MODULES = [
'core.definition', 'core.definition',
'core.utils', 'core.utils',
'core.classes.modules.config',
'core.base', 'core.base',
'core.module',
'core.classes.modules.config',
'core.classes.modules.commands', 'core.classes.modules.commands',
'core.classes.modules.user',
'core.classes.modules.admin',
'core.classes.modules.channel',
'core.classes.modules.reputation',
'core.classes.modules.sasl',
'core.classes.modules.rpc.rpc_channel', 'core.classes.modules.rpc.rpc_channel',
'core.classes.modules.rpc.rpc_command', 'core.classes.modules.rpc.rpc_command',
'core.classes.modules.rpc.rpc_user', 'core.classes.modules.rpc.rpc_user',
@@ -54,33 +62,35 @@ async def restart_service(uplink: 'Loader', reason: str = "Restarting with no re
uplink.ModuleUtils.model_clear() # Clear loaded modules. uplink.ModuleUtils.model_clear() # Clear loaded modules.
uplink.User.UID_DB.clear() # Clear User Object uplink.User.UID_DB.clear() # Clear User Object
uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object
uplink.Client.CLIENT_DB.clear() # Clear Client object
uplink.Irc.Protocol.Handler.DB_IRCDCOMMS.clear() uplink.Irc.Protocol.Handler.DB_IRCDCOMMS.clear()
# Reload Service modules # Reload Service modules
for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.reload_one_module(module.module_name, uplink.Settings.current_admin) await uplink.ModuleUtils.reload_one_module(module.module_name, uplink.Settings.current_admin)
uplink.Irc.signal = True
await uplink.Irc.run() await uplink.Irc.run()
uplink.Config.DEFENDER_RESTART = 0 uplink.Config.DEFENDER_RESTART = 0
async def rehash_service(uplink: 'Loader', nickname: str) -> None: async def rehash_service(uplink: 'Loader', nickname: str) -> None:
need_a_restart = ["SERVEUR_ID"] need_a_restart = ["SERVEUR_ID"]
uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS) uplink.Settings.set_cache('commands', uplink.Commands.DB_COMMANDS)
uplink.Settings.set_cache('users', uplink.User.UID_DB)
uplink.Settings.set_cache('admins', uplink.Admin.UID_ADMIN_DB)
uplink.Settings.set_cache('reputations', uplink.Reputation.UID_REPUTATION_DB)
uplink.Settings.set_cache('channels', uplink.Channel.UID_CHANNEL_DB)
uplink.Settings.set_cache('sasl', uplink.Sasl.DB_SASL)
uplink.Settings.set_cache('modules', uplink.ModuleUtils.DB_MODULES)
uplink.Settings.set_cache('module_headers', uplink.ModuleUtils.DB_MODULE_HEADERS)
await uplink.RpcServer.stop_rpc_server() await uplink.RpcServer.stop_rpc_server()
restart_flag = False restart_flag = False
config_model_bakcup = uplink.Config config_model_bakcup = uplink.Config
mods = REHASH_MODULES mods = REHASH_MODULES
_count_reloaded_modules = len(mods)
for mod in mods: for mod in mods:
importlib.reload(sys.modules[mod]) importlib.reload(sys.modules[mod])
await uplink.Irc.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f'[REHASH] Module [{mod}] reloaded',
channel=uplink.Config.SERVICE_CHANLOG
)
uplink.Utils = sys.modules['core.utils'] uplink.Utils = sys.modules['core.utils']
uplink.Config = uplink.ConfModule.Configuration(uplink).configuration_model uplink.Config = uplink.ConfModule.Configuration(uplink).configuration_model
uplink.Config.HSID = config_model_bakcup.HSID uplink.Config.HSID = config_model_bakcup.HSID
@@ -115,9 +125,25 @@ async def rehash_service(uplink: 'Loader', nickname: str) -> None:
# Reload Main Commands Module # Reload Main Commands Module
uplink.Commands = uplink.CommandModule.Command(uplink) uplink.Commands = uplink.CommandModule.Command(uplink)
uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('db_commands') uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('commands')
uplink.Base = uplink.BaseModule.Base(uplink) uplink.Base = uplink.BaseModule.Base(uplink)
uplink.User = user.User(uplink)
uplink.Admin = admin.Admin(uplink)
uplink.Channel = channel.Channel(uplink)
uplink.Reputation = reputation.Reputation(uplink)
uplink.ModuleUtils = module_mod.Module(uplink)
uplink.Sasl = sasl.Sasl(uplink)
# Backup data
uplink.User.UID_DB = uplink.Settings.get_cache('users')
uplink.Admin.UID_ADMIN_DB = uplink.Settings.get_cache('admins')
uplink.Channel.UID_CHANNEL_DB = uplink.Settings.get_cache('channels')
uplink.Reputation.UID_REPUTATION_DB = uplink.Settings.get_cache('reputations')
uplink.Sasl.DB_SASL = uplink.Settings.get_cache('sasl')
uplink.ModuleUtils.DB_MODULE_HEADERS = uplink.Settings.get_cache('module_headers')
uplink.ModuleUtils.DB_MODULES = uplink.Settings.get_cache('modules')
uplink.Irc.Protocol = uplink.PFactory.get() uplink.Irc.Protocol = uplink.PFactory.get()
uplink.Irc.Protocol.register_command() uplink.Irc.Protocol.register_command()
@@ -128,6 +154,15 @@ async def rehash_service(uplink: 'Loader', nickname: str) -> None:
for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.reload_one_module(module.module_name, nickname) await uplink.ModuleUtils.reload_one_module(module.module_name, nickname)
color_green = uplink.Config.COLORS.green
color_reset = uplink.Config.COLORS.nogc
await uplink.Irc.Protocol.send_priv_msg(
uplink.Config.SERVICE_NICKNAME,
tr("[ %sREHASH INFO%s ] Rehash completed! %s modules reloaded.", color_green, color_reset, _count_reloaded_modules),
uplink.Config.SERVICE_CHANLOG
)
return None return None
async def shutdown(uplink: 'Loader') -> None: async def shutdown(uplink: 'Loader') -> None:
@@ -141,6 +176,14 @@ async def shutdown(uplink: 'Loader') -> None:
for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.unload_one_module(module.module_name) await uplink.ModuleUtils.unload_one_module(module.module_name)
uplink.Logs.debug(f"=======> Closing all Sockets!")
for soc in uplink.Base.running_sockets:
soc.close()
while soc.fileno() != -1:
soc.close()
uplink.Base.running_sockets.remove(soc)
uplink.Logs.debug(f"> Socket ==> closed {str(soc.fileno())}")
# Nettoyage des timers # Nettoyage des timers
uplink.Logs.debug(f"=======> Closing all timers!") uplink.Logs.debug(f"=======> Closing all timers!")
for timer in uplink.Base.running_timers: for timer in uplink.Base.running_timers:
@@ -158,25 +201,37 @@ async def shutdown(uplink: 'Loader') -> None:
uplink.Logs.debug(f"> Cancelling {thread.name} {thread.native_id}") uplink.Logs.debug(f"> Cancelling {thread.name} {thread.native_id}")
uplink.Logs.debug(f"=======> Closing all IO Threads!") uplink.Logs.debug(f"=======> Closing all IO Threads!")
[th.thread_event.clear() for th in uplink.Base.running_iothreads] [th.thread_event.clear() for th in uplink.Base.running_iothreads if isinstance(th.thread_event, threading.Event)]
uplink.Logs.debug(f"=======> Closing all IO TASKS!") uplink.Logs.debug(f"=======> Closing all IO TASKS!")
t = None
for task in uplink.Base.running_iotasks:
if 'force_shutdown' == task.get_name():
t = task
if t:
uplink.Base.running_iotasks.remove(t)
task_already_canceled: list = []
for task in uplink.Base.running_iotasks:
try: try:
await asyncio.wait_for(asyncio.gather(*uplink.Base.running_iotasks), timeout=5) if not task.cancel():
print(task.get_name())
task_already_canceled.append(task)
except asyncio.exceptions.CancelledError as cerr:
uplink.Logs.debug(f"Asyncio CancelledError reached! {task}")
for task in task_already_canceled:
uplink.Base.running_iotasks.remove(task)
for task in uplink.Base.running_iotasks:
try:
await asyncio.wait_for(asyncio.gather(task), timeout=5)
except asyncio.exceptions.TimeoutError as te: except asyncio.exceptions.TimeoutError as te:
uplink.Logs.debug(f"Asyncio Timeout reached! {te}") uplink.Logs.debug(f"Asyncio Timeout reached! {te} {task}")
for task in uplink.Base.running_iotasks: for task in uplink.Base.running_iotasks:
task.cancel() task.cancel()
except asyncio.exceptions.CancelledError as cerr: except asyncio.exceptions.CancelledError as cerr:
uplink.Logs.debug(f"Asyncio CancelledError reached! {cerr}") uplink.Logs.debug(f"Asyncio CancelledError reached! {cerr} {task}")
uplink.Logs.debug(f"=======> Closing all Sockets!")
for soc in uplink.Base.running_sockets:
soc.close()
while soc.fileno() != -1:
soc.close()
uplink.Base.running_sockets.remove(soc)
uplink.Logs.debug(f"> Socket ==> closed {str(soc.fileno())}")
uplink.Base.running_timers.clear() uplink.Base.running_timers.clear()
uplink.Base.running_threads.clear() uplink.Base.running_threads.clear()
@@ -187,3 +242,9 @@ async def shutdown(uplink: 'Loader') -> None:
uplink.Base.db_close() uplink.Base.db_close()
return None return None
async def force_shutdown(uplink: 'Loader') -> None:
await asyncio.sleep(10)
uplink.Logs.critical("The system has been killed because something is blocking the loop")
uplink.Logs.critical(asyncio.all_tasks())
sys.exit('The system has been killed')

View File

@@ -8,7 +8,7 @@ from core.classes.interfaces.iprotocol import IProtocol
from core.utils import tr from core.utils import tr
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MSasl, MClient, MUser, MChannel from core.definition import MSasl, MUser, MChannel
class Inspircd(IProtocol): class Inspircd(IProtocol):
@@ -562,7 +562,6 @@ class Inspircd(IProtocol):
uid = str(scopy[0]).replace(':','') uid = str(scopy[0]).replace(':','')
newnickname = scopy[2] newnickname = scopy[2]
self._ctx.User.update_nickname(uid, newnickname) self._ctx.User.update_nickname(uid, newnickname)
self._ctx.Client.update_nickname(uid, newnickname)
self._ctx.Admin.update_nickname(uid, newnickname) self._ctx.Admin.update_nickname(uid, newnickname)
return None return None
@@ -1334,15 +1333,12 @@ class Inspircd(IProtocol):
client_uid (str): Client UID client_uid (str): Client UID
user_account (str): The account of the user user_account (str): The account of the user
""" """
... pass
async def send_svslogout(self, client_obj: 'MClient') -> None: async def send_svslogout(self) -> None:
"""Logout a client from his account """Logout a client from his account
Args:
client_obj (MClient): The Client Object Model
""" """
... pass
async def send_svsmode(self, nickname: str, user_mode: str) -> None: async def send_svsmode(self, nickname: str, user_mode: str) -> None:
"""_summary_ """_summary_

View File

@@ -7,7 +7,7 @@ from core.classes.interfaces.iprotocol import IProtocol
from core.utils import is_coroutinefunction, tr from core.utils import is_coroutinefunction, tr
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MClient, MSasl, MUser, MChannel from core.definition import MSasl, MUser, MChannel
class Unrealircd6(IProtocol): class Unrealircd6(IProtocol):
@@ -469,18 +469,13 @@ class Unrealircd6(IProtocol):
except Exception as err: except Exception as err:
self._ctx.Logs.error(f'General Error: {err}') self._ctx.Logs.error(f'General Error: {err}')
async def send_svslogout(self, client_obj: 'MClient') -> None: async def send_svslogout(self) -> None:
"""Logout a client from his account """Logout a client from his account
Args:
client_obj (MClient): The Client object
""" """
try: try:
c_uid = client_obj.uid # await self.send2socket(f":{self._ctx.Config.SERVEUR_LINK} SVSLOGIN {self._ctx.Settings.MAIN_SERVER_HOSTNAME} {c_uid} 0")
c_nickname = client_obj.nickname # await self.send_svs2mode(c_nickname, '-r')
await self.send2socket(f":{self._ctx.Config.SERVEUR_LINK} SVSLOGIN {self._ctx.Settings.MAIN_SERVER_HOSTNAME} {c_uid} 0") pass
await self.send_svs2mode(c_nickname, '-r')
except Exception as err: except Exception as err:
self._ctx.Logs.error(f'General Error: {err}') self._ctx.Logs.error(f'General Error: {err}')
@@ -510,7 +505,7 @@ class Unrealircd6(IProtocol):
return None return None
async def send_uid(self, nickname:str, username: str, hostname: str, uid:str, umodes: str, async 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: vhost: str, remote_ip: str, realname: str, geoip: str, print_log: bool = True) -> None:
"""Send UID to the server """Send UID to the server
- Insert User to User Object - Insert User to User Object
Args: Args:
@@ -535,7 +530,7 @@ class Unrealircd6(IProtocol):
self._ctx.Definition.MUser( self._ctx.Definition.MUser(
uid=uid, nickname=nickname, username=username, uid=uid, nickname=nickname, username=username,
realname=realname,hostname=hostname, umodes=umodes, realname=realname,hostname=hostname, umodes=umodes,
vhost=vhost, remote_ip=remote_ip vhost=vhost, remote_ip=remote_ip, geoip=geoip
) )
) )
@@ -795,7 +790,6 @@ class Unrealircd6(IProtocol):
self._ctx.Channel.delete_user_from_all_channel(uid_who_quit) self._ctx.Channel.delete_user_from_all_channel(uid_who_quit)
self._ctx.User.delete(uid_who_quit) self._ctx.User.delete(uid_who_quit)
self._ctx.Client.delete(uid_who_quit)
self._ctx.Reputation.delete(uid_who_quit) self._ctx.Reputation.delete(uid_who_quit)
self._ctx.Admin.delete(uid_who_quit) self._ctx.Admin.delete(uid_who_quit)
@@ -879,7 +873,6 @@ class Unrealircd6(IProtocol):
uid = str(server_msg[1]).lstrip(':') uid = str(server_msg[1]).lstrip(':')
newnickname = server_msg[3] newnickname = server_msg[3]
self._ctx.User.update_nickname(uid, newnickname) self._ctx.User.update_nickname(uid, newnickname)
self._ctx.Client.update_nickname(uid, newnickname)
self._ctx.Admin.update_nickname(uid, newnickname) self._ctx.Admin.update_nickname(uid, newnickname)
self._ctx.Reputation.update(uid, newnickname) self._ctx.Reputation.update(uid, newnickname)
@@ -1472,7 +1465,7 @@ class Unrealircd6(IProtocol):
sasl_obj.fingerprint = str(scopy[6]) sasl_obj.fingerprint = str(scopy[6])
await self.send2socket(f":{self._ctx.Config.SERVEUR_LINK} SASL {self._ctx.Settings.MAIN_SERVER_HOSTNAME} {sasl_obj.client_uid} C +") await self.send2socket(f":{self._ctx.Config.SERVEUR_LINK} SASL {self._ctx.Settings.MAIN_SERVER_HOSTNAME} {sasl_obj.client_uid} C +")
self.on_sasl_authentication_process(sasl_obj) await self.on_sasl_authentication_process(sasl_obj)
return sasl_obj return sasl_obj
case 'C': case 'C':

View File

@@ -28,26 +28,6 @@ class MainModel:
"""Return a list of attributes name""" """Return a list of attributes name"""
return [f.name for f in fields(self)] return [f.name for f in fields(self)]
@dataclass
class MClient(MainModel):
"""Model Client for registred nickname"""
uid: str = None
account: str = None
nickname: str = None
username: str = None
realname: str = None
hostname: str = None
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
score_connexion: int = 0
geoip: str = None
connexion_datetime: datetime = field(default=datetime.now())
@dataclass @dataclass
class MUser(MainModel): class MUser(MainModel):
"""Model User""" """Model User"""
@@ -283,9 +263,6 @@ class MConfig(MainModel):
LOGGING_NAME: str = "defender" LOGGING_NAME: str = "defender"
"""The name of the Logging instance""" """The name of the Logging instance"""
TABLE_CLIENT: str = "core_client"
"""Core Client table"""
TABLE_ADMIN: str = "core_admin" TABLE_ADMIN: str = "core_admin"
"""Core Admin table""" """Core Admin table"""

View File

@@ -2,14 +2,12 @@ import asyncio
import re import re
import ssl import ssl
import threading import threading
from datetime import datetime, timedelta from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING, Any, Optional, Union
from core.classes.modules import rehash from core.classes.modules import rehash
from core.classes.interfaces.iprotocol import IProtocol from core.classes.interfaces.iprotocol import IProtocol
from core.utils import tr from core.utils import tr
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MSasl
from core.loader import Loader from core.loader import Loader
class Irc: class Irc:
@@ -66,36 +64,32 @@ class Irc:
self.ctx.Commands.build_command(0, 'core', 'copyright', 'Give some information about the IRC Service') self.ctx.Commands.build_command(0, 'core', 'copyright', 'Give some information about the IRC Service')
self.ctx.Commands.build_command(0, 'core', 'uptime', 'Give you since when the service is connected') self.ctx.Commands.build_command(0, 'core', 'uptime', 'Give you since when the service is connected')
self.ctx.Commands.build_command(0, 'core', 'firstauth', 'First authentication of the Service') self.ctx.Commands.build_command(0, 'core', 'firstauth', 'First authentication of the Service')
self.ctx.Commands.build_command(0, 'core', 'register', f'Register your nickname /msg {self.ctx.Config.SERVICE_NICKNAME} REGISTER <password> <email>')
self.ctx.Commands.build_command(0, 'core', 'identify', f'Identify yourself with your password /msg {self.ctx.Config.SERVICE_NICKNAME} IDENTIFY <account> <password>')
self.ctx.Commands.build_command(0, 'core', 'logout', 'Reverse the effect of the identify command')
self.ctx.Commands.build_command(1, 'core', 'load', 'Load an existing module')
self.ctx.Commands.build_command(1, 'core', 'unload', 'Unload a module')
self.ctx.Commands.build_command(1, 'core', 'reload', 'Reload a module')
self.ctx.Commands.build_command(1, 'core', 'deauth', 'Deauth from the irc service') self.ctx.Commands.build_command(1, 'core', 'deauth', 'Deauth from the irc service')
self.ctx.Commands.build_command(1, 'core', 'checkversion', 'Check the version of the irc service') self.ctx.Commands.build_command(1, 'core', 'checkversion', 'Check the version of the irc service')
self.ctx.Commands.build_command(2, 'core', 'show_modules', 'Display a list of loaded modules') self.ctx.Commands.build_command(2, 'core', 'show_modules', 'Display a list of loaded modules')
self.ctx.Commands.build_command(2, 'core', 'show_timers', 'Display active timers')
self.ctx.Commands.build_command(2, 'core', 'show_threads', 'Display active threads in the system')
self.ctx.Commands.build_command(2, 'core', 'show_asyncio', 'Display active asyncio')
self.ctx.Commands.build_command(2, 'core', 'show_channels', 'Display a list of active channels') self.ctx.Commands.build_command(2, 'core', 'show_channels', 'Display a list of active channels')
self.ctx.Commands.build_command(2, 'core', 'show_users', 'Display a list of connected users') self.ctx.Commands.build_command(2, 'core', 'show_users', 'Display a list of connected users')
self.ctx.Commands.build_command(2, 'core', 'show_clients', 'Display a list of connected clients')
self.ctx.Commands.build_command(2, 'core', 'show_admins', 'Display a list of administrators') self.ctx.Commands.build_command(2, 'core', 'show_admins', 'Display a list of administrators')
self.ctx.Commands.build_command(2, 'core', 'show_configuration', 'Display the current configuration settings') self.ctx.Commands.build_command(2, 'core', 'show_configuration', 'Display the current configuration settings')
self.ctx.Commands.build_command(2, 'core', 'show_cache', 'Display the current cache') self.ctx.Commands.build_command(2, 'core', 'show_cache', 'Display the current cache')
self.ctx.Commands.build_command(2, 'core', 'clear_cache', 'Clear the cache!') self.ctx.Commands.build_command(2, 'core', 'clear_cache', 'Clear the cache!')
self.ctx.Commands.build_command(3, 'core', 'quit', 'Disconnect the bot or user from the server.')
self.ctx.Commands.build_command(3, 'core', 'restart', 'Restart the bot or service.')
self.ctx.Commands.build_command(3, 'core', 'addaccess', 'Add a user or entity to an access list with specific permissions.') self.ctx.Commands.build_command(3, 'core', 'addaccess', 'Add a user or entity to an access list with specific permissions.')
self.ctx.Commands.build_command(3, 'core', 'editaccess', 'Modify permissions for an existing user or entity in the access list.') self.ctx.Commands.build_command(3, 'core', 'editaccess', 'Modify permissions for an existing user or entity in the access list.')
self.ctx.Commands.build_command(3, 'core', 'delaccess', 'Remove a user or entity from the access list.') self.ctx.Commands.build_command(3, 'core', 'delaccess', 'Remove a user or entity from the access list.')
self.ctx.Commands.build_command(3, 'core', 'cert', 'Append your new fingerprint to your account!') self.ctx.Commands.build_command(3, 'core', 'cert', 'Append your new fingerprint to your account!')
self.ctx.Commands.build_command(4, 'core', 'quit', 'Disconnect the bot or user from the server.')
self.ctx.Commands.build_command(4, 'core', 'rehash', 'Reload the configuration file without restarting') self.ctx.Commands.build_command(4, 'core', 'rehash', 'Reload the configuration file without restarting')
self.ctx.Commands.build_command(4, 'core', 'restart', 'Restart the bot or service.')
self.ctx.Commands.build_command(4, 'core', 'raw', 'Send a raw command directly to the IRC server') self.ctx.Commands.build_command(4, 'core', 'raw', 'Send a raw command directly to the IRC server')
self.ctx.Commands.build_command(4, 'core', 'print_vars', 'Print users in a file.') self.ctx.Commands.build_command(4, 'core', 'print_vars', 'Print users in a file.')
self.ctx.Commands.build_command(4, 'core', 'show_timers', 'Display active timers')
self.ctx.Commands.build_command(4, 'core', 'show_threads', 'Display active threads in the system')
self.ctx.Commands.build_command(4, 'core', 'show_asyncio', 'Display active asyncio')
self.ctx.Commands.build_command(4, 'core', 'start_rpc', 'Start defender jsonrpc server') self.ctx.Commands.build_command(4, 'core', 'start_rpc', 'Start defender jsonrpc server')
self.ctx.Commands.build_command(4, 'core', 'stop_rpc', 'Stop defender jsonrpc server') self.ctx.Commands.build_command(4, 'core', 'stop_rpc', 'Stop defender jsonrpc server')
self.ctx.Commands.build_command(4, 'core', 'load', 'Load an existing module')
self.ctx.Commands.build_command(4, 'core', 'unload', 'Unload a module')
self.ctx.Commands.build_command(4, 'core', 'reload', 'Reload a module')
############################################## ##############################################
# CONNEXION IRC # # CONNEXION IRC #
@@ -103,6 +97,7 @@ class Irc:
async def run(self): async def run(self):
try: try:
self.signal = True
await self.connect() await self.connect()
await self.listen() await self.listen()
except asyncio.exceptions.IncompleteReadError as ie: except asyncio.exceptions.IncompleteReadError as ie:
@@ -137,6 +132,7 @@ class Irc:
data = await self.reader.readuntil(b'\r\n') data = await self.reader.readuntil(b'\r\n')
await self.send_response(data.splitlines()) await self.send_response(data.splitlines())
async def send_response(self, responses:list[bytes]) -> None: async def send_response(self, responses:list[bytes]) -> None:
try: try:
for data in responses: for data in responses:
@@ -206,65 +202,6 @@ class Irc:
return None return None
async def on_sasl_authentication_process(self, sasl_model: 'MSasl') -> bool:
s = sasl_model
if sasl_model:
async 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.ctx.Config.TABLE_ADMIN} WHERE fingerprint = :fingerprint"
else:
mes_donnees = {'user': username, 'password': self.ctx.Utils.hash_password(password)}
query = f"SELECT user, level, language FROM {self.ctx.Config.TABLE_ADMIN} WHERE user = :user AND password = :password"
result = await self.ctx.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 = await 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')
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} SASL {self.ctx.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S")
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful")
else:
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} SASL {self.ctx.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D F")
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} 904 {s.username} :SASL authentication failed")
elif s.message_type == 'S' and s.mechanisme == 'EXTERNAL':
# Connection using fingerprints
admin_info = await 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')
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} SASL {self.ctx.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S")
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful")
else:
# "904 <nick> :SASL authentication failed"
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} SASL {self.ctx.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D F")
await self.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_LINK} 904 {s.username} :SASL authentication failed")
def get_defender_uptime(self) -> str:
"""Savoir depuis quand Defender est connecté
Returns:
str: L'écart entre la date du jour et celle de la connexion de Defender
"""
current_datetime = datetime.now()
diff_date = current_datetime - self.defender_connexion_datetime
uptime = timedelta(days=diff_date.days, seconds=diff_date.seconds)
return uptime
def insert_db_admin(self, uid: str, account: str, level: int, language: str) -> None: def insert_db_admin(self, uid: str, account: str, level: int, language: str) -> None:
user_obj = self.ctx.User.get_user(uid) user_obj = self.ctx.User.get_user(uid)
@@ -330,7 +267,7 @@ class Irc:
spassword = self.ctx.Utils.hash_password(password) spassword = self.ctx.Utils.hash_password(password)
# Check if the user already exist # Check if the user already exist
if not self.ctx.Admin.db_is_admin_exist(nickname): if not await self.ctx.Admin.db_is_admin_exist(nickname):
mes_donnees = {'datetime': self.ctx.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level, 'language': self.ctx.Config.LANG} mes_donnees = {'datetime': self.ctx.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level, 'language': self.ctx.Config.LANG}
await self.ctx.Base.db_execute_query(f'''INSERT INTO {self.ctx.Config.TABLE_ADMIN} await self.ctx.Base.db_execute_query(f'''INSERT INTO {self.ctx.Config.TABLE_ADMIN}
(createdOn, user, password, hostname, vhost, level, language) VALUES (createdOn, user, password, hostname, vhost, level, language) VALUES
@@ -347,7 +284,7 @@ class Irc:
async def thread_check_for_new_version(self, fromuser: str) -> None: async def thread_check_for_new_version(self, fromuser: str) -> None:
dnickname = self.ctx.Config.SERVICE_NICKNAME dnickname = self.ctx.Config.SERVICE_NICKNAME
response = self.ctx.Base.create_asynctask( response = await self.ctx.Base.create_asynctask(
self.ctx.Base.create_thread_io( self.ctx.Base.create_thread_io(
self.ctx.Base.check_for_new_version, True self.ctx.Base.check_for_new_version, True
) )
@@ -405,9 +342,6 @@ class Irc:
if u is None: if u is None:
return None return None
c = self.ctx.Client.get_client(u.uid)
"""The Client Object"""
fromuser = u.nickname fromuser = u.nickname
uid = u.uid uid = u.uid
self.ctx.Settings.current_admin = self.ctx.Admin.get_admin(user) # set Current admin if any. self.ctx.Settings.current_admin = self.ctx.Admin.get_admin(user) # set Current admin if any.
@@ -609,10 +543,19 @@ class Irc:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"level: from 1 to 4") await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"level: from 1 to 4")
return None return None
if len(cmd) == 4:
user_new_level = int(cmd[3])
if user_new_level > 5:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Maximum authorized level is 5")
return None
if user_new_level == 0:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You must provide a valid level [1 to 4]")
return None
user_to_edit = cmd[1] user_to_edit = cmd[1]
user_password = self.ctx.Utils.hash_password(cmd[2]) user_password = self.ctx.Utils.hash_password(cmd[2])
get_admin = self.ctx.Admin.get_admin(fromuser) get_admin = self.ctx.Admin.get_admin(fromuser)
if get_admin is None: if get_admin is None:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {fromuser} has no Admin access") await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {fromuser} has no Admin access")
return None return None
@@ -621,15 +564,9 @@ class Irc:
current_uid = uid current_uid = uid
current_user_level = get_admin.level current_user_level = get_admin.level
user_new_level = int(cmd[3]) if len(cmd) == 4 else get_admin.level if current_user == user_to_edit:
if current_user == fromuser:
user_new_level = get_admin.level user_new_level = get_admin.level
if user_new_level > 5:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Maximum authorized level is 5")
return None
# Rechercher le user dans la base de données. # Rechercher le user dans la base de données.
mes_donnees = {'user': user_to_edit} mes_donnees = {'user': user_to_edit}
query = f"SELECT user, level FROM {self.ctx.Config.TABLE_ADMIN} WHERE user = :user" query = f"SELECT user, level FROM {self.ctx.Config.TABLE_ADMIN} WHERE user = :user"
@@ -664,6 +601,10 @@ class Irc:
case 'delaccess': case 'delaccess':
# .delaccess [USER] [CONFIRMUSER] # .delaccess [USER] [CONFIRMUSER]
if len(cmd) < 3:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Right command : /msg {dnickname} delaccess [nickname] [CONFIRMNICKNAME]")
return None
user_to_del = cmd[1] user_to_del = cmd[1]
user_confirmation = cmd[2] user_confirmation = cmd[2]
@@ -672,10 +613,6 @@ class Irc:
self.ctx.Logs.warning(f':{dnickname} NOTICE {fromuser} : Les user ne sont pas les mêmes, tu dois confirmer le user que tu veux supprimer') self.ctx.Logs.warning(f':{dnickname} NOTICE {fromuser} : Les user ne sont pas les mêmes, tu dois confirmer le user que tu veux supprimer')
return None return None
if len(cmd) < 3:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{self.ctx.Config.SERVICE_PREFIX}delaccess [USER] [CONFIRMUSER]")
return None
get_admin = self.ctx.Admin.get_admin(fromuser) get_admin = self.ctx.Admin.get_admin(fromuser)
if get_admin is None: if get_admin is None:
@@ -747,147 +684,6 @@ class Irc:
except Exception as e: except Exception as e:
self.ctx.Logs.error(e) self.ctx.Logs.error(e)
case 'register':
# Syntax. Register PASSWORD EMAIL
try:
if len(cmd) < 3:
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f'/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} <PASSWORD> <EMAIL>'
)
return None
password = cmd[1]
email = cmd[2]
if not self.ctx.Base.is_valid_email(email_to_control=email):
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg='The email is not valid. You must provide a valid email address (first.name@email.extension)'
)
return None
user_obj = u
if user_obj is None:
self.ctx.Logs.error(f"Nickname ({fromuser}) doesn't exist, it is impossible to register this nickname")
return None
# If the account already exist.
if self.ctx.Client.db_is_account_exist(fromuser):
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"Your account already exist, please try to login instead /msg {self.ctx.Config.SERVICE_NICKNAME} IDENTIFY <account> <password>"
)
return None
# If the account doesn't exist then insert into database
data_to_record = {
'createdOn': self.ctx.Utils.get_sdatetime(), 'account': fromuser,
'nickname': user_obj.nickname, 'hostname': user_obj.hostname, 'vhost': user_obj.vhost, 'realname': user_obj.realname, 'email': email,
'password': self.ctx.Utils.hash_password(password=password), 'level': 0
}
insert_to_db = await self.ctx.Base.db_execute_query(f"""
INSERT INTO {self.ctx.Config.TABLE_CLIENT}
(createdOn, account, nickname, hostname, vhost, realname, email, password, level)
VALUES
(:createdOn, :account, :nickname, :hostname, :vhost, :realname, :email, :password, :level)
""", data_to_record)
if insert_to_db.rowcount > 0:
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"You have register your nickname successfully"
)
return None
except ValueError as ve:
self.ctx.Logs.error(f"Value Error : {ve}")
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {self.ctx.Config.SERVICE_PREFIX}{command.upper()} <PASSWORD> <EMAIL>")
case 'identify':
# Identify ACCOUNT PASSWORD
try:
if len(cmd) < 3:
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f'/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} <ACCOUNT> <PASSWORD>'
)
return None
account = str(cmd[1]) # account
encrypted_password = self.ctx.Utils.hash_password(cmd[2])
user_obj = u
client_obj = c
if client_obj is not None:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You are already logged in")
return None
db_query = f"SELECT account FROM {self.ctx.Config.TABLE_CLIENT} WHERE account = :account AND password = :password"
db_param = {'account': account, 'password': encrypted_password}
exec_query = await self.ctx.Base.db_execute_query(db_query, db_param)
result_query = exec_query.fetchone()
if result_query:
account = result_query[0]
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You are now logged in")
client = self.ctx.Definition.MClient(**user_obj.to_dict(), account=account)
self.ctx.Client.insert(client)
await self.Protocol.send_svslogin(user_obj.uid, account)
await self.Protocol.send_svs2mode(nickname=fromuser, user_mode='+r')
else:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Wrong password or account")
return None
except ValueError as ve:
self.ctx.Logs.error(f"Value Error: {ve}")
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f'/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} <ACCOUNT> <PASSWORD>'
)
except Exception as err:
self.ctx.Logs.error(f"General Error: {err}")
case 'logout':
try:
# LOGOUT <account>
if len(cmd) < 2:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} <account>")
return None
user_obj = u
if user_obj is None:
self.ctx.Logs.error(f"The User [{fromuser}] is not available in the database")
return None
client_obj = c
if client_obj is None:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Nothing to logout. please login first")
return None
await self.Protocol.send_svslogout(client_obj)
self.ctx.Client.delete(user_obj.uid)
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You have been logged out successfully")
except ValueError as ve:
self.ctx.Logs.error(f"Value Error: {ve}")
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} <account>")
except Exception as err:
self.ctx.Logs.error(f"General Error: {err}")
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} <account>")
case 'help': case 'help':
# Syntax. !help [module_name] # Syntax. !help [module_name]
module_name = str(cmd[1]) if len(cmd) == 2 else None module_name = str(cmd[1]) if len(cmd) == 2 else None
@@ -946,15 +742,13 @@ class Irc:
try: try:
final_reason = ' '.join(cmd[1:]) final_reason = ' '.join(cmd[1:])
self.hb_active = False self.hb_active = False
self.ctx.Base.create_asynctask(rehash.force_shutdown(self.ctx), run_once=True)
await rehash.shutdown(self.ctx) await rehash.shutdown(self.ctx)
self.ctx.Base.execute_periodic_action() self.ctx.Base.execute_periodic_action()
for chan_name in self.ctx.Channel.UID_CHANNEL_DB: for chan_name in self.ctx.Channel.UID_CHANNEL_DB:
await self.Protocol.send_set_mode('-l', channel_name=chan_name.name) await self.Protocol.send_set_mode('-l', channel_name=chan_name.name)
for client in self.ctx.Client.CLIENT_DB:
await self.Protocol.send_svslogout(client)
await self.Protocol.send_notice( await self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
@@ -1088,17 +882,6 @@ class Irc:
) )
return None return None
case 'show_clients':
count_users = len(self.ctx.Client.CLIENT_DB)
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Total Connected Clients: {count_users}")
for db_client in self.ctx.Client.CLIENT_DB:
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"UID : {db_client.uid} - isWebirc: {db_client.isWebirc} - isWebSocket: {db_client.isWebsocket} - Nickname: {db_client.nickname} - Account: {db_client.account} - Connection: {db_client.connexion_datetime}"
)
return None
case 'show_admins': case 'show_admins':
for db_admin in self.ctx.Admin.UID_ADMIN_DB: for db_admin in self.ctx.Admin.UID_ADMIN_DB:
await self.Protocol.send_notice( await self.Protocol.send_notice(
@@ -1135,7 +918,7 @@ class Irc:
return None return None
case 'uptime': case 'uptime':
uptime = self.get_defender_uptime() uptime = self.ctx.Utils.get_defender_uptime(self.ctx)
await self.Protocol.send_notice( await self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
@@ -1187,4 +970,3 @@ class Irc:
case _: case _:
pass pass

View File

@@ -1,4 +1,5 @@
traduction: traduction:
# Message help # Message help
- orig: "Access denied!" - orig: "Access denied!"
trad: "Accès refusé." trad: "Accès refusé."
@@ -11,11 +12,34 @@ traduction:
- orig: "cmd method is not available in the module (%s)" - orig: "cmd method is not available in the module (%s)"
trad: "La méthode cmd n'est pas disponible dans le module (%s)" trad: "La méthode cmd n'est pas disponible dans le module (%s)"
- orig: "[%sMODULE ERROR%s] Module %s is facing issues! %s" - orig: "[%sMODULE ERROR%s] Module %s is facing issues! %s"
trad: "[%sMODULE ERREUR%s] Le module %s a rencontré une erreur! %s" trad: "[%sERREUR MODULE%s] Le module %s a rencontré une erreur! %s"
- orig: "%s - %sNot Loaded%s" - orig: "%s - %sNot Loaded%s"
trad: "%s - %sNon chargé%s" trad: "%s - %sNon chargé%s"
- orig: "Successfuly connected to %s" - orig: "Successfuly connected to %s"
trad: "Connecter a %s avec succés" trad: "Connecté à %s avec succés"
- orig: "[ %sINFORMATION%s ] >> %s is ready!" - orig: "[ %sINFORMATION%s ] >> %s is ready!"
trad: "[ %sINFORMATION%s ] >> %s est prêt!" trad: "[ %sINFORMATION%s ] >> %s est prêt!"
# core/irc.py
- orig: "[ %s%s%s ] - %s has been disconnected from %s"
trad: "[ %s%s%s ] - %s a été déconnecté de %s"
- orig: "You can't use this command anymore ! Please use [%sauth] instead"
trad: "Vous ne pouvez plus utiliser cette commande ! Utilisez [%auth] à la place."
- orig: "The nickname sent [%s] is different than the one set in the configuration file!"
trad: "Le pseudo transmis, [%s], ne correspond pas à celui renseigné dans le fichier de configuration !"
- orig: "[%s %s %s] - %s is now connected to %s"
trad: "[%s %s %s] - %s est désormais connecté à %s"
- orig: "[ %s %s %s ] - %s provided a wrong password!"
trad: "[ %s %s %s ] - %s a donné un mauvais mot de passe !"
- orig: "%s - %sNot Loaded%s"
trad: "%s - Non chargé%s"
# core/classe/protocols/unreal6.py & core/classe/protocols/inspircd.py
- orig: "[ %sINFORMATION%s ] >> %s is ready!"
trad: "[ %sINFORMATION%s ] >> %s est prêt !"
- orig: "[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s"
trad: "[ %sSASL AUTH%s ] - %s (%s) s'est connecté avec succès à %s"
- orig: "[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s"
trad: "[ %sSASL AUTH%s ] - %s : Mauvais certificat fourni avec le compte %s"
- orig: "[ %sFINGERPRINT AUTH%s ] - %s (%s) is now connected successfuly to %s"
trad: "[ %sFINGERPRINT AUTH%s ] - %s (%s) est maintenant authentifié sur %s !"

View File

@@ -1,6 +1,6 @@
from logging import Logger from logging import Logger
from core.classes.modules.settings import global_settings from core.classes.modules.settings import global_settings
from core.classes.modules import translation, user, admin, client, channel, reputation, settings, sasl from core.classes.modules import translation, user, admin, channel, reputation, settings, sasl
import core.logs as logs import core.logs as logs
import core.definition as df import core.definition as df
import core.utils as utils import core.utils as utils
@@ -63,8 +63,6 @@ class Loader:
self.Settings.global_user = self.User self.Settings.global_user = self.User
self.Client: client.Client = client.Client(self)
self.Admin: admin.Admin = admin.Admin(self) self.Admin: admin.Admin = admin.Admin(self)
self.Channel: channel.Channel = channel.Channel(self) self.Channel: channel.Channel = channel.Channel(self)

View File

@@ -22,6 +22,25 @@ class Module:
def __init__(self, loader: 'Loader') -> None: def __init__(self, loader: 'Loader') -> None:
self._ctx = loader self._ctx = loader
def is_module_compliant(self, obj: object) -> bool:
class_name = obj.__name__
is_compliant = True
attributs = {'MOD_HEADER', 'mod_config'}
methods = {'load', 'unload', 'create_tables', 'cmd', 'hcmds', 'ModConfModel'}
obj_attributs: set = set([attribut for attribut in dir(obj) if not callable(getattr(obj, attribut)) and not attribut.startswith('__')])
obj_methods: set = set([method for method in dir(obj) if callable(getattr(obj, method)) and not method.startswith('__')])
if not attributs.issubset(obj_attributs):
self._ctx.Logs.error(f'[{class_name}] Your module is not valid make sure you have implemented required attributes {attributs}')
raise AttributeError(f'[{class_name}] Your module is not valid make sure you have implemented required attributes {attributs}')
if not methods.issubset(obj_methods):
self._ctx.Logs.error(f'[{class_name}] Your module is not valid make sure you have implemented required methods {methods}')
raise AttributeError(f'[{class_name}] Your module is not valid make sure you have implemented required methods {methods}')
return is_compliant
def get_all_available_modules(self) -> list[str]: def get_all_available_modules(self) -> list[str]:
"""Get list of all main modules """Get list of all main modules
using this pattern mod_*.py using this pattern mod_*.py
@@ -100,13 +119,14 @@ class Module:
channel=self._ctx.Config.SERVICE_CHANLOG channel=self._ctx.Config.SERVICE_CHANLOG
) )
return False return False
reload_mod = await self.reload_one_module(module_name, nickname)
return self.reload_one_module(module_name, nickname) return reload_mod
# Charger le module # Charger le module
try: try:
loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}')
my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe
self.is_module_compliant(my_class)
create_instance_of_the_class: 'IModule' = my_class(self._ctx) # Créer une nouvelle instance de la classe create_instance_of_the_class: 'IModule' = my_class(self._ctx) # Créer une nouvelle instance de la classe
await create_instance_of_the_class.load() if self._ctx.Utils.is_coroutinefunction(create_instance_of_the_class.load) else create_instance_of_the_class.load() await create_instance_of_the_class.load() if self._ctx.Utils.is_coroutinefunction(create_instance_of_the_class.load) else create_instance_of_the_class.load()
self.create_module_header(create_instance_of_the_class.MOD_HEADER) self.create_module_header(create_instance_of_the_class.MOD_HEADER)
@@ -118,17 +138,9 @@ class Module:
msg=tr("[%sMODULE ERROR%s] Module %s is facing issues! %s", red, nogc, module_name, attr), msg=tr("[%sMODULE ERROR%s] Module %s is facing issues! %s", red, nogc, module_name, attr),
channel=self._ctx.Config.SERVICE_CHANLOG channel=self._ctx.Config.SERVICE_CHANLOG
) )
self._ctx.Logs.error(msg=attr, exc_info=True) self.drop_module_from_sys_modules(module_name)
return False
if not hasattr(create_instance_of_the_class, 'cmd'):
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=tr("cmd method is not available in the module (%s)", module_name),
channel=self._ctx.Config.SERVICE_CHANLOG
)
self._ctx.Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available")
await self.db_delete_module(module_name) await self.db_delete_module(module_name)
self._ctx.Logs.error(msg=attr, exc_info=True)
return False return False
# Charger la nouvelle class dans la variable globale # Charger la nouvelle class dans la variable globale
@@ -184,6 +196,7 @@ class Module:
the_module = sys.modules[f'mods.{module_folder}.{module_name}'] the_module = sys.modules[f'mods.{module_folder}.{module_name}']
importlib.reload(the_module) importlib.reload(the_module)
my_class = getattr(the_module, class_name, None) my_class = getattr(the_module, class_name, None)
self.is_module_compliant(my_class)
new_instance: 'IModule' = my_class(self._ctx) new_instance: 'IModule' = my_class(self._ctx)
await new_instance.load() if self._ctx.Utils.is_coroutinefunction(new_instance.load) else new_instance.load() await new_instance.load() if self._ctx.Utils.is_coroutinefunction(new_instance.load) else new_instance.load()
self.create_module_header(new_instance.MOD_HEADER) self.create_module_header(new_instance.MOD_HEADER)
@@ -215,7 +228,9 @@ class Module:
msg=f"[RELOAD MODULE ERROR]: {err}", msg=f"[RELOAD MODULE ERROR]: {err}",
channel=self._ctx.Config.SERVICE_CHANLOG channel=self._ctx.Config.SERVICE_CHANLOG
) )
self.drop_module_from_sys_modules(module_name)
await self.db_delete_module(module_name) await self.db_delete_module(module_name)
return False
def reload_all_modules(self) -> bool: def reload_all_modules(self) -> bool:
... ...
@@ -323,6 +338,24 @@ class Module:
self._ctx.Logs.debug(f"[SYS MODULE] (mods.{module_folder}.{module_name}) not found in sys.modules") self._ctx.Logs.debug(f"[SYS MODULE] (mods.{module_folder}.{module_name}) not found in sys.modules")
return False return False
def drop_module_from_sys_modules(self, module_name: str) -> bool:
"""_summary_
Args:
module_name (str): _description_
Returns:
bool: _description_
"""
module_folder, module_name, class_name = self.get_module_information(module_name)
full_module_name = "mods." + module_folder + "." + module_name
del sys.modules[full_module_name]
if not self.is_module_exist_in_sys_module(module_name):
return True
return False
''' '''
ALL METHODS RELATED TO THE MModule MODEL DATACLASS ALL METHODS RELATED TO THE MModule MODEL DATACLASS
''' '''
@@ -343,6 +376,22 @@ class Module:
self._ctx.Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES") self._ctx.Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES")
return None return None
def model_drop_module(self, module_name: str) -> bool:
"""Drop a module model object from DB_MODULES
Args:
module_name (str): The module name you want to drop
Returns:
bool: True if the model has been dropped
"""
module = self.model_get_module(module_name)
if module:
self.DB_MODULES.remove(module)
return True
return False
def model_get_loaded_modules(self) -> list[MModule]: def model_get_loaded_modules(self) -> list[MModule]:
"""Get the instance of DB_MODULES. """Get the instance of DB_MODULES.
Warning: You should use a copy if you want to loop through the list! Warning: You should use a copy if you want to loop through the list!

View File

@@ -5,9 +5,8 @@ import gc
import ssl import ssl
from pathlib import Path from pathlib import Path
from re import match, sub from re import match, sub
import threading
from typing import Literal, Optional, Any, TYPE_CHECKING from typing import Literal, Optional, Any, TYPE_CHECKING
from datetime import datetime from datetime import datetime, timedelta
from time import time, sleep from time import time, sleep
from random import choice from random import choice
from hashlib import md5, sha3_512 from hashlib import md5, sha3_512
@@ -115,6 +114,18 @@ def get_ssl_context() -> ssl.SSLContext:
ctx.verify_mode = ssl.CERT_NONE ctx.verify_mode = ssl.CERT_NONE
return ctx return ctx
def get_defender_uptime(loader: 'Loader') -> str:
"""Savoir depuis quand Defender est connecté
Returns:
str: L'écart entre la date du jour et celle de la connexion de Defender
"""
current_datetime = datetime.now()
diff_date = current_datetime - loader.Irc.defender_connexion_datetime
uptime = timedelta(days=diff_date.days, seconds=diff_date.seconds)
return uptime
def run_python_garbage_collector() -> int: def run_python_garbage_collector() -> int:
"""Run Python garbage collector """Run Python garbage collector

View File

@@ -11,7 +11,7 @@ from core import install
############################################# #############################################
async def main(): async def main():
install.update_packages() # install.update_packages()
from core.loader import Loader from core.loader import Loader
loader = Loader() loader = Loader()
await loader.start() await loader.start()

View File

@@ -17,6 +17,7 @@ class MClone(MainModel):
hostname: str = 'localhost' hostname: str = 'localhost'
umodes: str = None umodes: str = None
remote_ip: str = '127.0.0.1' remote_ip: str = '127.0.0.1'
group: str = 'Default' group: str = 'Default',
geoip: str = 'XX'
# DB_CLONES: list[MClone] = [] # DB_CLONES: list[MClone] = []

View File

@@ -28,7 +28,7 @@ async def coro_connect_clones(uplink: 'Clone',
break break
if not clone.connected: if not clone.connected:
await uplink.ctx.Irc.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, print_log=False) await uplink.ctx.Irc.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, clone.geoip, print_log=False)
await uplink.ctx.Irc.Protocol.send_join_chan(uidornickname=clone.uid, channel=uplink.ctx.Config.CLONE_CHANNEL, password=uplink.ctx.Config.CLONE_CHANNEL_PASSWORD, print_log=False) await uplink.ctx.Irc.Protocol.send_join_chan(uidornickname=clone.uid, channel=uplink.ctx.Config.CLONE_CHANNEL, password=uplink.ctx.Config.CLONE_CHANNEL_PASSWORD, print_log=False)
await asyncio.sleep(interval) await asyncio.sleep(interval)

View File

@@ -103,6 +103,17 @@ def generate_ipv4_for_clone(faker_instance: 'Faker', auto: bool = True) -> str:
""" """
return faker_instance.ipv4_private() if auto else '127.0.0.1' return faker_instance.ipv4_private() if auto else '127.0.0.1'
def generate_country_code_for_clone(faker_instance: 'Faker') -> str:
"""Generate the alpha-2 country code for clone
Args:
faker_instance (Faker): The Faker Instance
Returns:
str: The Country Code
"""
return faker_instance.country_code('alpha-2')
def generate_hostname_for_clone(faker_instance: 'Faker') -> str: def generate_hostname_for_clone(faker_instance: 'Faker') -> str:
"""Generate hostname for clone """Generate hostname for clone
@@ -143,6 +154,8 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
hostname = generate_hostname_for_clone(faker) hostname = generate_hostname_for_clone(faker)
vhost = generate_vhost_for_clone(faker) vhost = generate_vhost_for_clone(faker)
geoip = generate_country_code_for_clone(faker)
checkNickname = uplink.Clone.nickname_exists(nickname) checkNickname = uplink.Clone.nickname_exists(nickname)
checkUid = uplink.Clone.uid_exists(uid=uid) checkUid = uplink.Clone.uid_exists(uid=uid)
@@ -167,7 +180,8 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
remote_ip=decoded_ip, remote_ip=decoded_ip,
vhost=vhost, vhost=vhost,
group=group, group=group,
channels=[] channels=[],
geoip=geoip
) )
uplink.Clone.insert(clone) uplink.Clone.insert(clone)

View File

@@ -31,7 +31,7 @@ class Command(IModule):
def mod_config(self) -> ModConfModel: def mod_config(self) -> ModConfModel:
return self._mod_config return self._mod_config
def create_tables(self) -> None: async def create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args: Args:
@@ -51,10 +51,14 @@ class Command(IModule):
) )
''' '''
self.ctx.Base.db_execute_query(table_automode) await self.ctx.Base.db_execute_query(table_automode)
return None return None
def load(self) -> None: async def load(self) -> None:
# Create the database
await self.create_tables()
# Module Utils # Module Utils
self.mod_utils = utils self.mod_utils = utils
self.user_to_notice: str = '' self.user_to_notice: str = ''
@@ -66,9 +70,7 @@ class Command(IModule):
self.ctx.Irc.Protocol.known_protocol.add(c) self.ctx.Irc.Protocol.known_protocol.add(c)
self.ctx.Commands.build_command(2, self.module_name, 'join', 'Join a channel') self.ctx.Commands.build_command(2, self.module_name, 'join', 'Join a channel')
self.ctx.Commands.build_command(2, self.module_name, 'assign', 'Assign a user to a role or task')
self.ctx.Commands.build_command(2, self.module_name, 'part', 'Leave a channel') self.ctx.Commands.build_command(2, self.module_name, 'part', 'Leave a channel')
self.ctx.Commands.build_command(2, self.module_name, 'unassign', 'Remove a user from a role or task')
self.ctx.Commands.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user') self.ctx.Commands.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user')
self.ctx.Commands.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user') self.ctx.Commands.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user')
self.ctx.Commands.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked') self.ctx.Commands.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked')
@@ -214,16 +216,12 @@ class Command(IModule):
user_uid = self.ctx.User.clean_uid(cmd[5]) user_uid = self.ctx.User.clean_uid(cmd[5])
userObj: MUser = self.ctx.User.get_user(user_uid) userObj: MUser = self.ctx.User.get_user(user_uid)
channel_name = cmd[4] if self.ctx.Channel.is_valid_channel(cmd[4]) else None channel_name = cmd[4] if self.ctx.Channel.is_valid_channel(cmd[4]) else None
client_obj = self.ctx.Client.get_client(user_uid)
nickname = userObj.nickname if userObj is not None else None nickname = userObj.nickname if userObj is not None else None
if client_obj is not None:
nickname = client_obj.account
if userObj is None: if userObj is None:
return None return None
if 'r' not in userObj.umodes and 'o' not in userObj.umodes and not self.ctx.Client.is_exist(userObj.uid): if 'r' not in userObj.umodes and 'o' not in userObj.umodes:
return None return None
db_data: dict[str, str] = {"nickname": nickname.lower(), "channel": channel_name.lower()} db_data: dict[str, str] = {"nickname": nickname.lower(), "channel": channel_name.lower()}
@@ -412,7 +410,7 @@ class Command(IModule):
except Exception as err: except Exception as err:
self.ctx.Logs.warning(f'Unknown Error: {str(err)}') self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
case 'join' | 'assign': case 'join':
try: try:
await self.mod_utils.set_assign_channel_to_service(self, cmd, fromuser) await self.mod_utils.set_assign_channel_to_service(self, cmd, fromuser)
except IndexError as ie: except IndexError as ie:
@@ -421,7 +419,7 @@ class Command(IModule):
except Exception as err: except Exception as err:
self.ctx.Logs.warning(f'Unknown Error: {str(err)}') self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
case 'part' | 'unassign': case 'part':
try: try:
# Syntax. !part #channel # Syntax. !part #channel
await self.mod_utils.set_unassign_channel_to_service(self, cmd, fromuser) await self.mod_utils.set_unassign_channel_to_service(self, cmd, fromuser)

View File

@@ -115,9 +115,11 @@ async def set_mode_to_all(uplink: 'Command', channel_name: str, action: Literal[
users:str = '' users:str = ''
uids_split = [chan_info.uids[i:i + 6] for i in range(0, len(chan_info.uids), 6)] uids_split = [chan_info.uids[i:i + 6] for i in range(0, len(chan_info.uids), 6)]
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{set_mode} {dnickname}") # await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{set_mode} {dnickname}")
for uid in uids_split: for uid in uids_split:
for i in range(0, len(uid)): for i in range(0, len(uid)):
if uplink.ctx.Utils.clean_uid(uid[i]) == uplink.ctx.Config.SERVICE_ID:
continue
mode += set_mode mode += set_mode
users += f'{uplink.ctx.User.get_nickname(uplink.ctx.Utils.clean_uid(uid[i]))} ' users += f'{uplink.ctx.User.get_nickname(uplink.ctx.Utils.clean_uid(uid[i]))} '
if i == len(uid) - 1: if i == len(uid) - 1:

View File

@@ -288,10 +288,19 @@ class Defender(IModule):
jail_chan = self.ctx.Config.SALON_JAIL # Salon pot de miel jail_chan = self.ctx.Config.SALON_JAIL # Salon pot de miel
jail_chan_mode = self.ctx.Config.SALON_JAIL_MODES # Mode du salon "pot de miel" jail_chan_mode = self.ctx.Config.SALON_JAIL_MODES # Mode du salon "pot de miel"
color_green = self.ctx.Config.COLORS.green
color_red = self.ctx.Config.COLORS.red
color_black = self.ctx.Config.COLORS.black
color_nogc = self.ctx.Config.COLORS.nogc
match command: match command:
case 'show_reputation': case 'show_reputation':
if self.mod_config.reputation == 0:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Reputation system if off!")
return None
if not self.ctx.Reputation.UID_REPUTATION_DB: if not self.ctx.Reputation.UID_REPUTATION_DB:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="No one is suspected") await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="No one is suspected")
@@ -664,9 +673,6 @@ class Defender(IModule):
# .proxy_scan set psutil_scan on/off --> Active les informations de connexion a la machine locale # .proxy_scan set psutil_scan on/off --> Active les informations de connexion a la machine locale
# .proxy_scan set abuseipdb_scan on/off --> Active le scan via l'api abuseipdb # .proxy_scan set abuseipdb_scan on/off --> Active le scan via l'api abuseipdb
len_cmd = len(cmd) len_cmd = len(cmd)
color_green = self.ctx.Config.COLORS.green
color_red = self.ctx.Config.COLORS.red
color_black = self.ctx.Config.COLORS.black
if len_cmd == 4: if len_cmd == 4:
set_key = str(cmd[1]).lower() set_key = str(cmd[1]).lower()
@@ -901,7 +907,7 @@ class Defender(IModule):
UserObject = self.ctx.User.get_user(nickoruid) UserObject = self.ctx.User.get_user(nickoruid)
if UserObject is not None: if UserObject is not None:
channels: list = [chan.name for chan in self.ctx.Channel.UID_CHANNEL_DB for uid_in_chan in chan.uids if self.ctx.mod_utils.clean_uid(uid_in_chan) == UserObject.uid] channels: list = [chan.name for chan in self.ctx.Channel.UID_CHANNEL_DB for uid_in_chan in chan.uids if self.ctx.User.clean_uid(uid_in_chan) == UserObject.uid]
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' UID : {UserObject.uid}') await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' UID : {UserObject.uid}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' NICKNAME : {UserObject.nickname}') await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' NICKNAME : {UserObject.nickname}')
@@ -925,6 +931,10 @@ class Defender(IModule):
case 'sentinel': case 'sentinel':
# .sentinel on # .sentinel on
if len(cmd) < 2:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Syntax. /msg {dnickname} sentinel [ON | OFF]")
return None
activation = str(cmd[1]).lower() activation = str(cmd[1]).lower()
channel_to_dont_quit = [self.ctx.Config.SALON_JAIL, self.ctx.Config.SERVICE_CHANLOG] channel_to_dont_quit = [self.ctx.Config.SALON_JAIL, self.ctx.Config.SERVICE_CHANLOG]
@@ -939,6 +949,7 @@ class Defender(IModule):
if chan.name not in channel_to_dont_quit: if chan.name not in channel_to_dont_quit:
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=dnickname, channel=chan.name) await self.ctx.Irc.Protocol.send_join_chan(uidornickname=dnickname, channel=chan.name)
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"Sentinel mode activated on {channel}", channel=chan.name) await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"Sentinel mode activated on {channel}", channel=chan.name)
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"[ {color_green}SENTINEL{color_nogc} ] Activated by {fromuser}", channel=self.ctx.Config.SERVICE_CHANLOG)
return None return None
if activation == 'off': if activation == 'off':
@@ -953,7 +964,7 @@ class Defender(IModule):
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"Sentinel mode deactivated on {channel}", channel=chan.name) await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"Sentinel mode deactivated on {channel}", channel=chan.name)
await self.join_saved_channels() await self.join_saved_channels()
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"[ {color_red}SENTINEL{color_nogc} ] Deactivated by {fromuser}", channel=self.ctx.Config.SERVICE_CHANLOG)
return None return None
case _: case _:

View File

@@ -227,6 +227,7 @@ async def coro_psutil_scan(uplink: 'Defender'):
for user in uplink.Schemas.DB_PSUTIL_USERS: for user in uplink.Schemas.DB_PSUTIL_USERS:
result = await uplink.ctx.Base.create_thread_io(uplink.mod_utils.action_scan_client_with_psutil, uplink, user) result = await uplink.ctx.Base.create_thread_io(uplink.mod_utils.action_scan_client_with_psutil, uplink, user)
list_to_remove.append(user) list_to_remove.append(user)
if not result: if not result:
continue continue

View File

@@ -534,8 +534,14 @@ def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUser') -
'Accept': 'application/json', 'Accept': 'application/json',
'Key': uplink.abuseipdb_key 'Key': uplink.abuseipdb_key
} }
try:
response = sess.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout) response = sess.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout)
except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectTimeout) as err:
uplink.ctx.Logs.error(f"Time-out Error: {err}")
return None
except Exception as e:
uplink.ctx.Logs.error(f"Time-out Error: {e}")
return None
if response.status_code != 200: if response.status_code != 200:
uplink.ctx.Logs.warning(f'status code = {str(response.status_code)}') uplink.ctx.Logs.warning(f'status code = {str(response.status_code)}')

View File

@@ -26,11 +26,15 @@ class Test(IModule):
} }
"""Module Header (Mandatory)""" """Module Header (Mandatory)"""
@property
def mod_config(self) -> ModConfModel:
return self._mod_config
def __init__(self, uplink: 'Loader'): def __init__(self, uplink: 'Loader'):
super().__init__(uplink) super().__init__(uplink)
self._mod_config: Optional[Test.ModConfModel] = None self._mod_config: Optional[Test.ModConfModel] = None
def create_tables(self) -> None: async def create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
@@ -45,12 +49,14 @@ class Test(IModule):
) )
''' '''
# self.ctx.Base.db_execute_query(table_logs) # await self.ctx.Base.db_execute_query(table_logs)
return None return None
async def load(self) -> None: async def load(self) -> None:
"""### Load Module Configuration (Mandatory) """### Load Module Configuration (Mandatory)
""" """
# Create tables if any (Mandatory)
await self.create_tables()
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.ctx.Commands.build_command(0, self.module_name, 'test-command', 'Execute a test command') self.ctx.Commands.build_command(0, self.module_name, 'test-command', 'Execute a test command')
@@ -68,15 +74,16 @@ class Test(IModule):
if self.mod_config.param_exemple2 == 1: if self.mod_config.param_exemple2 == 1:
await self.ctx.Irc.Protocol.send_priv_msg(self.ctx.Config.SERVICE_NICKNAME, "Param activated", self.ctx.Config.SERVICE_CHANLOG) await self.ctx.Irc.Protocol.send_priv_msg(self.ctx.Config.SERVICE_NICKNAME, "Param activated", self.ctx.Config.SERVICE_CHANLOG)
@property
def mod_config(self) -> ModConfModel:
return self._mod_config
def unload(self) -> None: def unload(self) -> None:
"""### This method is called when you unload, or you reload the module (Mandatory)""" """### This method is called when you unload, or you reload the module (Mandatory)"""
self.ctx.Commands.drop_command_by_module(self.module_name) self.ctx.Commands.drop_command_by_module(self.module_name)
return None return None
async def asyncio_func(self) -> None:
self.ctx.Logs.debug(f"Starting async method in a task: {self.__class__.__name__}")
await asyncio.sleep(2)
self.ctx.Logs.debug(f"End of the task: {self.__class__.__name__}")
def cmd(self, data: list[str]) -> None: def cmd(self, data: list[str]) -> None:
"""All messages coming from the IRCD server will be handled using this method (Mandatory) """All messages coming from the IRCD server will be handled using this method (Mandatory)
@@ -89,11 +96,6 @@ class Test(IModule):
except Exception as err: except Exception as err:
self.ctx.Logs.error(f"General Error: {err}") self.ctx.Logs.error(f"General Error: {err}")
async def asyncio_func(self) -> None:
self.ctx.Logs.debug(f"Starting async method in a task: {self.__class__.__name__}")
await asyncio.sleep(2)
self.ctx.Logs.debug(f"End of the task: {self.__class__.__name__}")
async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: Optional[list] = None) -> None: async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: Optional[list] = None) -> None:
"""All messages coming from the user commands (Mandatory) """All messages coming from the user commands (Mandatory)
@@ -126,8 +128,8 @@ class Test(IModule):
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=c.name) await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=c.name)
# How to update your module configuration # How to update your module configuration
self.update_configuration('param_exemple2', 7) await self.update_configuration('param_exemple2', 7)
self.update_configuration('param_exemple1', 'my_value') await self.update_configuration('param_exemple1', 'my_value')
# Log if you want the result # Log if you want the result
self.ctx.Logs.debug(f"Test logs ready") self.ctx.Logs.debug(f"Test logs ready")

View File

@@ -43,7 +43,7 @@ class Votekick(IModule):
def mod_config(self) -> ModConfModel: def mod_config(self) -> ModConfModel:
return self._mod_config return self._mod_config
def create_tables(self) -> None: async def create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
@@ -65,12 +65,15 @@ class Votekick(IModule):
) )
''' '''
self.ctx.Base.db_execute_query(table_logs) await self.ctx.Base.db_execute_query(table_logs)
self.ctx.Base.db_execute_query(table_vote) await self.ctx.Base.db_execute_query(table_vote)
return None return None
async def load(self) -> None: async def load(self) -> None:
# Create tables.
await self.create_tables()
self._mod_config = self.ModConfModel() self._mod_config = self.ModConfModel()
await self.sync_db() await self.sync_db()

View File

@@ -144,7 +144,7 @@ class VotekickManager:
votec = self.get_vote_channel_model(channel_name) votec = self.get_vote_channel_model(channel_name)
if votec: if votec:
client_obj = self.uplink.User.get_user(votec.target_user) client_obj = self.uplink.ctx.User.get_user(votec.target_user)
client_to_punish = votec.target_user if client_obj is None else client_obj.nickname client_to_punish = votec.target_user if client_obj is None else client_obj.nickname
if nickname in votec.voter_users: if nickname in votec.voter_users:
self.Logs.debug(f"[VOTEKICK MANAGER] This nickname ({nickname}) has already voted for ({client_to_punish})") self.Logs.debug(f"[VOTEKICK MANAGER] This nickname ({nickname}) has already voted for ({client_to_punish})")

View File

@@ -1,5 +1,5 @@
{ {
"version": "6.4.0", "version": "6.4.1",
"requests": "2.32.5", "requests": "2.32.5",
"psutil": "7.1.2", "psutil": "7.1.2",