30 Commits

Author SHA1 Message Date
adator
b182aa8bcb Fix mod_jsonrpc module! Selecting the correct parameter based on the JSONRPC_METHOD value in the configruation.yaml 2025-10-29 00:02:57 +01:00
adator
e5a5f01603 Adding 'make update' to update from git repository. remove previous installation.py file, the update of packages is done via install.py file. 2025-10-28 23:33:43 +01:00
adator
99f8949681 Create Makefile installation; update copyright core command. TODO replace installation.py script. 2025-10-28 01:02:27 +01:00
adator
05b15f2f18 Merge pull request #95 from adator85/fix-install
Fix the previous configuration.json check in the installation file
2025-10-26 21:16:38 +01:00
adator
35c3faf68c Fix the previous configuration.json check in the installation file 2025-10-26 21:15:05 +01:00
adator
2e9bfd2c3b Merge pull request #94 from adator85/v6.3.2
V6.3.2
2025-10-26 21:10:39 +01:00
adator
80131b7b7a Update version.json 2025-10-26 21:10:19 +01:00
adator
ffb30f12ec Remove json configuration and replace it by yaml configuration files. 2025-10-26 21:00:50 +01:00
adator
b7b61081be Fix get_datetime call and update some docstring. 2025-10-25 00:10:32 +02:00
adator
030b706b65 Update the private message when fingerprint auth is used 2025-10-19 20:42:35 +02:00
adator
c428ea2b41 If no fingerprint available, cert command will trigger an error. 2025-10-19 12:33:30 +02:00
adator
9036e4f626 Merge pull request #93 from adator85/v6.3.0
update mod_jsonrpc and configuration file
2025-10-18 22:48:20 +02:00
adator
fd79ada13d update mod_jsonrpc and configuration file 2025-10-18 22:46:38 +02:00
adator
8323f6cc9b Merge pull request #92 from adator85/v6.3.0
V6.3.0
2025-10-18 20:54:35 +02:00
adator
6fcd553481 Merge branch 'main' into v6.3.0 2025-10-18 20:54:26 +02:00
adator
5cd82a174d remove .vscode/ folder 2025-10-18 20:53:35 +02:00
adator
beec16f39d Update to the 3.0.0 V 2025-10-18 20:49:39 +02:00
adator
a043a58f45 Update to the 3.0.0 V 2025-10-18 20:49:21 +02:00
adator
fd9643eddc Add command handler system. Starting adapt the modules to fit other protocls. 2025-09-09 22:37:41 +02:00
adator
ed1a048603 Merge pull request #91 from adator85/v6.2.5-fix
Fix reputation issue by adding tls_cipher in the datamodel
2025-09-03 22:10:21 +02:00
adator
3dfde9b1aa Fix reputation issue by adding tls_cipher in the datamodel 2025-09-03 22:09:29 +02:00
adator
5e35a10193 Merge pull request #90 from adator85/v6.2.5-fix
Fix reputation issue by adding tls_cipher in the datamodel
2025-09-03 22:06:15 +02:00
adator
ff776541d7 Fix reputation issue by adding tls_cipher in the datamodel 2025-09-03 22:05:34 +02:00
adator
6b7fd16a44 Connectecting to inspircd 2025-09-03 22:01:52 +02:00
adator
e79c15188e Quick updates:
- Set default language for admins when running the db patch
    - Updating addaccess command.
    - Update levels for some commands in mod_command.
2025-08-30 23:09:03 +02:00
adator
b306968115 Merge pull request #89 from adator85/v6.2.5
V6.2.5
2025-08-29 21:45:20 +02:00
adator
184e90adce New updates changelog:
- Update info command (mod_defender.py)
    - Update help on commands (mod_clone.py)
2025-08-29 21:43:22 +02:00
adator
c7b88150b5 New updates for v6.2.5:
- Adding tls_cipher to MUser, MAdmin and MClient
    - Switch parser command in Irc Instance (To Monitor closly)
    - New auth method in Admin.py
    - Adding the capability to auto auth Admins via their fingerprints
    - Update few core translation.
2025-08-27 00:52:48 +02:00
adator
02f0608b75 Adding current_admin to the global settings:
- New property current_admin added to the Settings.py
    - Fix also translation function
2025-08-26 01:41:15 +02:00
adator
25bbddf459 Adding language field to admin db and local variable 2025-08-25 23:22:50 +02:00
43 changed files with 2787 additions and 1119 deletions

4
.gitignore vendored
View File

@@ -1,9 +1,13 @@
.pyenv/ .pyenv/
.vscode/
.venv/ .venv/
.idea/ .idea/
db/ db/
logs/ logs/
__pycache__/ __pycache__/
configuration.json configuration.json
configuration.yaml
configuration_inspircd.json
configuration_unreal6.json
*.log *.log
test.py test.py

62
Makefile Normal file
View File

@@ -0,0 +1,62 @@
OS := $(shell uname -s)
CURRENT_USER := $(shell whoami)
PYTHON_VERSION := $(shell python3 -V)
HOME_DIR := $(shell echo $$HOME)
SHELL := /bin/bash
install:
ifeq ($(wildcard config/configuration.yaml),)
$(error You must provide the Configuration file: config/configuration.yaml)
endif
ifeq ($(OS), Linux)
$(info Installation for os : $(OS))
$(info Python version: $(PYTHON_VERSION))
$(info Home directory: $(HOME_DIR))
@python3 core/install.py --check-version
@if [ $$? -eq 0 ]; then \
echo "Python Version OK! Well done :)"; \
else \
echo "Error: Script failed with exit code $$?"; \
exit 1; \
fi
$(info Creating the systemd user folder...)
mkdir -p $(HOME_DIR)/.config/systemd/user
$(info Creating Python Virtual Environment...)
python3 -m venv .pyenv
@. .pyenv/bin/activate && \
python -m pip install --upgrade pip && \
pip cache purge && \
pip install -r requirements.txt
@. .pyenv/bin/activate && python core/install.py --install
loginctl enable-linger $(CURRENT_USER)
@sleep 2
@export echo $DBUS_SESSION_BUS_ADDRESS && \
systemctl --user daemon-reload && \
systemctl --user start defender
endif
clean:
ifeq ($(OS), Linux)
@if [ -e .pyenv ]; then \
rm -rf .pyenv; \
echo "Virtual Env has been removed!"; \
fi
@if [ -e $(HOME_DIR)/.config/systemd/user/defender.service ]; then \
rm $(HOME_DIR)/.config/systemd/user/defender.service; \
echo "Systemd file has been removed!"; \
fi
@export echo $DBUS_SESSION_BUS_ADDRESS && systemctl --user daemon-reload && echo "Systemd Daemon reloaded!"
endif
update:
ifeq ($(OS), Linux)
$(info Starting update from the main repository...)
@. .pyenv/bin/activate && python core/install.py --git-update
$(info Update done!)
endif

139
README.md
View File

@@ -34,10 +34,11 @@ Il permet aux opérateurs de gérer efficacement un canal, tout en offrant aux u
- Python version 3.10 ou supérieure - Python version 3.10 ou supérieure
```bash ```bash
# Bash # Bash
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git $ git clone https://github.com/adator85/DEFENDER.git defender
$ cd defender/
# Renommer le fichier exemple_configuration.json en configuration.json # Renommer le fichier exemple_configuration.json en configuration.json
# Configurer le fichier configuration.json # Configurer le fichier configuration.json
$ python3 main.py $ make install
``` ```
Si votre configuration est bonne, votre service est censé etre connecté a votre réseau IRC Si votre configuration est bonne, votre service est censé etre connecté a votre réseau IRC
Pour Les prochains lancement de defender vous devez utiliser la commande suivante: Pour Les prochains lancement de defender vous devez utiliser la commande suivante:
@@ -49,11 +50,11 @@ Pour Les prochains lancement de defender vous devez utiliser la commande suivant
# Installation manuelle: # Installation manuelle:
```bash ```bash
# Bash # Bash
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git $ git clone https://github.com/adator85/DEFENDER.git defender
$ cd IRC_DEFENDER_MODULES $ cd defender/
$ python3 -m venv .pyenv $ python3 -m venv .pyenv
$ source .pyenv/bin/activate $ source .pyenv/bin/activate
(pyenv)$ pip install sqlalchemy, psutil, requests, faker, unrealircd_rpc_py (pyenv)$ pip install -r requirements.txt
# Créer un service nommé "defender.service" # Créer un service nommé "defender.service"
# pour votre service et placer le dans "/PATH/TO/USER/.config/systemd/user/" # pour votre service et placer le dans "/PATH/TO/USER/.config/systemd/user/"
@@ -104,87 +105,91 @@ Pour Les prochains lancement de defender vous devez utiliser la commande suivant
GLINE_DURATION: Durée de bannissement temporaire d'un utilisateur en minutes. (default : "30") GLINE_DURATION: Durée de bannissement temporaire d'un utilisateur en minutes. (default : "30")
DEBUG (Debug) DEBUG (Debug)
DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus grand est le nombre, plus il y a d'informations). (default : 20) Pour une production DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus petit est le nombre, plus il y a d'informations). (default : 20) Pour une production
DEBUG_HARD: Généralement utiliser pour les developpeurs.
``` ```
Modification de la configuration Modification de la configuration
Vous devez modifier le fichier configuration.json en remplaçant les valeurs par défaut avec vos propres informations. Assurez-vous de bien lire la description de chaque paramètre pour une configuration optimale du service. Vous devez modifier le fichier configuration.yaml en remplaçant les valeurs par défaut avec vos propres informations. Assurez-vous de bien lire la description de chaque paramètre pour une configuration optimale du service.
## Exemple de configuration de base ## Exemple de configuration de base
```json ```yaml
{ configuration:
"SERVEUR_IP": "IP.DE.TON.SERVER", SERVEUR_IP: "YOUR.SERVER.IP"
"SERVEUR_HOSTNAME": "HOST.DE.TON.SERVER", SERVEUR_HOSTNAME: "YOUR.SERVER.HOST"
"SERVEUR_LINK": "LINK.DE.TON.SERVER", SERVEUR_LINK: "LINK.DE.TON.SERVER"
"SERVEUR_PORT": 6901, SERVEUR_PORT: 7002
"SERVEUR_PASSWORD": "MOT_DE_PASS_DE_TON_LINK", SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD"
"SERVEUR_ID": "10Z", SERVEUR_ID: "006"
"SERVEUR_SSL": true, SERVEUR_SSL: true
"SERVICE_NAME": "defender", SERVICE_NAME: "defender"
"SERVICE_NICKNAME": "PyDefender", SERVICE_NICKNAME: "PyDefender"
"SERVICE_REALNAME": "Python Defender Security", SERVICE_REALNAME: "Python Defender Security"
"SERVICE_USERNAME": "PyDefender", SERVICE_USERNAME: "PyDefender"
"SERVICE_HOST": "HOST.DE.TON.DEFENDER", SERVICE_HOST: "HOST.DE.TON.DEFENDER"
SERVICE_INFO: "Network IRC Service"
SERVICE_CHANLOG: "#services"
SERVICE_SMODES: "+ioqBS"
SERVICE_CMODES: "ntsOP"
SERVICE_UMODES: "o"
SERVICE_PREFIX: "!"
"OWNER": "TON_NICK_NAME", OWNER: "TON_NICK_NAME"
"PASSWORD": "TON_PASSWORD" PASSWORD: "TON_PASSWORD"
}
``` ```
## Exemple complet de configuration ## Exemple complet de configuration
```json ```yaml
{ configuration:
"SERVEUR_IP": "YOUR.SERVER.IP", SERVEUR_IP: "YOUR.SERVER.IP"
"SERVEUR_HOSTNAME": "YOUR.SERVER.HOST", SERVEUR_HOSTNAME: "YOUR.SERVER.HOST"
"SERVEUR_LINK": "LINK.DE.TON.SERVER", SERVEUR_LINK: "LINK.DE.TON.SERVER"
"SERVEUR_PORT": 6901, SERVEUR_PORT: 7002
"SERVEUR_PASSWORD": "YOUR_LINK_PASSWORD", SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD"
"SERVEUR_ID": "10Z", SERVEUR_ID: "006"
"SERVEUR_SSL": true, SERVEUR_SSL: true
"SERVICE_NAME": "defender", SERVICE_NAME: "defender"
"SERVICE_NICKNAME": "PyDefender", SERVICE_NICKNAME: "PyDefender"
"SERVICE_REALNAME": "Python Defender Security", SERVICE_REALNAME: "Python Defender Security"
"SERVICE_USERNAME": "PyDefender", SERVICE_USERNAME: "PyDefender"
"SERVICE_HOST": "HOST.DE.TON.DEFENDER", SERVICE_HOST: "HOST.DE.TON.DEFENDER"
"SERVICE_INFO": "Network IRC Service", SERVICE_INFO: "Network IRC Service"
"SERVICE_CHANLOG": "#services", SERVICE_CHANLOG: "#services"
"SERVICE_SMODES": "+ioqBS", SERVICE_SMODES: "+ioqBS"
"SERVICE_CMODES": "ntsOP", SERVICE_CMODES: "ntsOP"
"SERVICE_UMODES": "o", SERVICE_UMODES: "o"
"SERVICE_PREFIX": "!", SERVICE_PREFIX: "!"
"OWNER": "TON_NICK_NAME", OWNER: "TON_NICK_NAME"
"PASSWORD": "TON_PASSWORD", PASSWORD: "TON_PASSWORD"
"JSONRPC_URL": "https://your.domaine.com:8600/api", JSONRPC_URL: "https://your.domaine.com:8600/api"
"JSONRPC_PATH_TO_SOCKET_FILE": "/PATH/TO/YOUR/IRCD/data/rpc.socket", JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket"
"JSONRPC_METHOD": "socket", JSONRPC_METHOD: "unixsocket"
"JSONRPC_USER": "YOUR_RPC_USER", JSONRPC_USER: "YOUR_RPC_USER"
"JSONRPC_PASSWORD": "YOUR_RPC_PASSWORD", JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD"
"SALON_JAIL": "#jail", SALON_JAIL: "#jail"
"SALON_JAIL_MODES": "sS", SALON_JAIL_MODES: "sS"
"SALON_LIBERER": "#welcome", SALON_LIBERER: "#welcome"
"CLONE_CHANNEL": "#clones", CLONE_CHANNEL: "#clones"
"CLONE_CMODES": "+nts", CLONE_CMODES: "+nts"
"CLONE_LOG_HOST_EXEMPT": ["HOST.TO.SKIP"], CLONE_LOG_HOST_EXEMPT: ["HOST.TO.SKIP"]
"CLONE_CHANNEL_PASSWORD": "YOUR_CHANNEL_PASSWORD", CLONE_CHANNEL_PASSWORD: "YOUR_CHANNEL_PASSWORD"
"API_TIMEOUT": 2, API_TIMEOUT: 2
"PORTS_TO_SCAN": [3028, 8080, 1080, 1085, 4145, 9050], PORTS_TO_SCAN: [3028 8080 1080 1085 4145 9050]
"WHITELISTED_IP": ["127.0.0.1"], WHITELISTED_IP: ["127.0.0.1"]
"GLINE_DURATION": "30", GLINE_DURATION: "30"
"DEBUG_LEVEL": 20 DEBUG_LEVEL: 20
DEBUG_HARD: true
}
``` ```
# \\!/ Attention \\!/ # \\!/ Attention \\!/
@@ -192,7 +197,7 @@ Le mot de passe de l'administrateur et le mot de passe du service doivent être
Ne partagez pas vos informations de connexion au serveur IRC avec des tiers. Ne partagez pas vos informations de connexion au serveur IRC avec des tiers.
a votre premiere connexion vous devez tapez a votre premiere connexion vous devez tapez
``` ```
/msg [NomDuService] auth [nickname] [password] /msg [NomDuService] firstauth [nickname] [password]
-- Une fois identifié tapez la commande suivante -- Une fois identifié tapez la commande suivante
/msg [NomDuService] editaccess [nickname] [Nouveau-Password] 5 /msg [NomDuService] editaccess [nickname] [Nouveau-Password] 5
``` ```

View File

@@ -1,48 +0,0 @@
{
"SERVEUR_IP": "YOUR.SERVER.IP",
"SERVEUR_HOSTNAME": "YOUR.SERVER.HOST",
"SERVEUR_LINK": "LINK.DE.TON.SERVER",
"SERVEUR_PORT": 7002,
"SERVEUR_PASSWORD": "YOUR_LINK_PASSWORD",
"SERVEUR_ID": "006",
"SERVEUR_SSL": true,
"SERVICE_NAME": "defender",
"SERVICE_NICKNAME": "PyDefender",
"SERVICE_REALNAME": "Python Defender Security",
"SERVICE_USERNAME": "PyDefender",
"SERVICE_HOST": "HOST.DE.TON.DEFENDER",
"SERVICE_INFO": "Network IRC Service",
"SERVICE_CHANLOG": "#services",
"SERVICE_SMODES": "+ioqBS",
"SERVICE_CMODES": "ntsOP",
"SERVICE_UMODES": "o",
"SERVICE_PREFIX": "!",
"OWNER": "TON_NICK_NAME",
"PASSWORD": "TON_PASSWORD",
"JSONRPC_URL": "https://your.domaine.com:8600/api",
"JSONRPC_PATH_TO_SOCKET_FILE": "/PATH/TO/YOUR/IRCD/data/rpc.socket",
"JSONRPC_METHOD": "socket",
"JSONRPC_USER": "YOUR_RPC_USER",
"JSONRPC_PASSWORD": "YOUR_RPC_PASSWORD",
"SALON_JAIL": "#jail",
"SALON_JAIL_MODES": "sS",
"SALON_LIBERER": "#welcome",
"CLONE_CHANNEL": "#clones",
"CLONE_CMODES": "+nts",
"CLONE_LOG_HOST_EXEMPT": ["HOST.TO.SKIP"],
"CLONE_CHANNEL_PASSWORD": "YOUR_CHANNEL_PASSWORD",
"API_TIMEOUT": 2,
"PORTS_TO_SCAN": [3028, 8080, 1080, 1085, 4145, 9050],
"WHITELISTED_IP": ["127.0.0.1"],
"GLINE_DURATION": "30",
"DEBUG_LEVEL": 20
}

View File

@@ -0,0 +1,47 @@
configuration:
SERVEUR_IP: "YOUR.SERVER.IP"
SERVEUR_HOSTNAME: "YOUR.SERVER.HOST"
SERVEUR_LINK: "LINK.DE.TON.SERVER"
SERVEUR_PORT: 7002
SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD"
SERVEUR_ID: "006"
SERVEUR_SSL: true
SERVICE_NAME: "defender"
SERVICE_NICKNAME: "PyDefender"
SERVICE_REALNAME: "Python Defender Security"
SERVICE_USERNAME: "PyDefender"
SERVICE_HOST: "HOST.DE.TON.DEFENDER"
SERVICE_INFO: "Network IRC Service"
SERVICE_CHANLOG: "#services"
SERVICE_SMODES: "+ioqBS"
SERVICE_CMODES: "ntsOP"
SERVICE_UMODES: "o"
SERVICE_PREFIX: "!"
OWNER: "TON_NICK_NAME"
PASSWORD: "TON_PASSWORD"
JSONRPC_URL: "https://your.domaine.com:8600/api"
JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket"
JSONRPC_METHOD: "unixsocket"
JSONRPC_USER: "YOUR_RPC_USER"
JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD"
SALON_JAIL: "#jail"
SALON_JAIL_MODES: "sS"
SALON_LIBERER: "#welcome"
CLONE_CHANNEL: "#clones"
CLONE_CMODES: "+nts"
CLONE_LOG_HOST_EXEMPT: ["HOST.TO.SKIP"]
CLONE_CHANNEL_PASSWORD: "YOUR_CHANNEL_PASSWORD"
API_TIMEOUT: 2
PORTS_TO_SCAN: [3028 8080 1080 1085 4145 9050]
WHITELISTED_IP: ["127.0.0.1"]
GLINE_DURATION: "30"
DEBUG_LEVEL: 20
DEBUG_HARD: true

View File

@@ -296,13 +296,14 @@ class Base:
'password': password, 'password': password,
'hostname': '*', 'hostname': '*',
'vhost': '*', 'vhost': '*',
'language': 'EN',
'level': 5 'level': 5
} }
self.db_execute_query(f""" self.db_execute_query(f"""
INSERT INTO {self.Config.TABLE_ADMIN} INSERT INTO {self.Config.TABLE_ADMIN}
(createdOn, user, password, hostname, vhost, level) (createdOn, user, password, hostname, vhost, language, level)
VALUES VALUES
(:createdOn, :user, :password, :hostname, :vhost, :level)""" (:createdOn, :user, :password, :hostname, :vhost, :language, :level)"""
, mes_donnees) , mes_donnees)
return None return None
@@ -599,7 +600,9 @@ class Base:
def db_patch(self, table_name: str, column_name: str, column_type: str) -> bool: 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): 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}" 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(patch)
self.db_execute_query(update_row)
self.logs.debug(f"The patch has been applied") 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}") self.logs.debug(f"Table name: {table_name}, Column name: {column_name}, Column type: {column_type}")
return True return True
@@ -680,14 +683,13 @@ class Base:
return False return False
def decode_ip(self, ip_b64encoded: str) -> Optional[str]: def decode_ip(self, ip_b64encoded: str) -> Optional[str]:
binary_ip = b64decode(ip_b64encoded)
try: try:
binary_ip = b64decode(ip_b64encoded)
decoded_ip = ipaddress.ip_address(binary_ip) decoded_ip = ipaddress.ip_address(binary_ip)
return decoded_ip.exploded return decoded_ip.exploded
except ValueError as ve: 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 return None
def encode_ip(self, remote_ip_address: str) -> Optional[str]: def encode_ip(self, remote_ip_address: str) -> Optional[str]:

View File

@@ -9,7 +9,17 @@ class Admin:
UID_ADMIN_DB: list[MAdmin] = [] UID_ADMIN_DB: list[MAdmin] = []
def __init__(self, loader: 'Loader') -> None: def __init__(self, loader: 'Loader') -> None:
"""
Args:
loader (Loader): The Loader Instance.
"""
self.Logs = loader.Logs self.Logs = loader.Logs
self.Base = loader.Base
self.Setting = loader.Settings
self.Config = loader.Config
self.User = loader.User
self.Definition = loader.Definition
def insert(self, new_admin: MAdmin) -> bool: def insert(self, new_admin: MAdmin) -> bool:
"""Insert a new admin object model """Insert a new admin object model
@@ -153,3 +163,65 @@ class Admin:
return record.nickname return record.nickname
return None return None
def get_language(self, uidornickname: str) -> Optional[str]:
"""Get the language of the admin
Args:
uidornickname (str): The user ID or the Nickname of the admin
Returns:
Optional[str]: The language selected by the admin.
"""
admin = self.get_admin(uidornickname)
if admin is None:
return None
return admin.language
def db_auth_admin_via_fingerprint(self, fp: str, uidornickname: str) -> bool:
"""Check the fingerprint
Args:
fp (str): The unique fingerprint of the user
uidornickname (str): The UID or the Nickname of the user
Returns:
bool: True if found
"""
query = f"SELECT user, level, language FROM {self.Config.TABLE_ADMIN} WHERE fingerprint = :fp"
data = {'fp': fp}
exe = self.Base.db_execute_query(query, data)
result = exe.fetchone()
if result:
account = result[0]
level = result[1]
language = result[2]
user_obj = self.User.get_user(uidornickname)
if user_obj:
admin_obj = self.Definition.MAdmin(**user_obj.to_dict(),account=account, level=level, language=language)
if self.insert(admin_obj):
self.Setting.current_admin = admin_obj
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

View File

@@ -11,14 +11,16 @@ class Channel:
"""List that contains all the Channels objects (ChannelModel) """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.Logs = loader.Logs
self.Base = loader.Base self.Base = loader.Base
self.Utils = loader.Utils self.Utils = loader.Utils
return None
def insert(self, new_channel: 'MChannel') -> bool: 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) """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 return result
except ValueError as ve: except ValueError as ve:
self.Logs.error(f'{ve}') self.Logs.error(f'{ve}')
return False
def delete_user_from_all_channel(self, uid:str) -> bool: def delete_user_from_all_channel(self, uid:str) -> bool:
"""Delete a client from all channels """Delete a client from all channels
@@ -134,6 +137,7 @@ class Channel:
return result return result
except ValueError as ve: except ValueError as ve:
self.Logs.error(f'{ve}') self.Logs.error(f'{ve}')
return False
def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool: def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool:
"""Add a client to a channel """Add a client to a channel
@@ -226,16 +230,18 @@ class Channel:
return False return False
pattern = fr'^#' pattern = fr'^#'
isChannel = findall(pattern, channel_to_check) is_channel = findall(pattern, channel_to_check)
if not isChannel: if not is_channel:
return False return False
else: else:
return True return True
except TypeError as te: except TypeError as te:
self.Logs.error(f'TypeError: [{channel_to_check}] - {te}') self.Logs.error(f'TypeError: [{channel_to_check}] - {te}')
return False
except Exception as err: except Exception as err:
self.Logs.error(f'Error Not defined: {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: 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. """You can add a channel or delete a channel.
@@ -282,8 +288,7 @@ class Channel:
else: else:
return False return False
case _:
return False
except Exception as err: except Exception as err:
self.Logs.error(err) self.Logs.error(err)
return False

View File

@@ -10,7 +10,11 @@ class Client:
CLIENT_DB: list['MClient'] = [] CLIENT_DB: list['MClient'] = []
def __init__(self, loader: 'Loader'): def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader instance.
"""
self.Logs = loader.Logs self.Logs = loader.Logs
self.Base = loader.Base self.Base = loader.Base
@@ -34,12 +38,12 @@ class Client:
return True 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 """Update the nickname starting from the UID
Args: Args:
uid (str): UID of the user uid (str): UID of the user
newNickname (str): New nickname new_nickname (str): New nickname
Returns: Returns:
bool: True if updated bool: True if updated
@@ -49,7 +53,7 @@ class Client:
if user_obj is None: if user_obj is None:
return False return False
user_obj.nickname = newNickname user_obj.nickname = new_nickname
return True return True
@@ -181,7 +185,7 @@ class Client:
return client_obj.to_dict() 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 """Check if the UID or the nickname exist in the USER DB
Args: Args:
@@ -190,7 +194,7 @@ class Client:
Returns: Returns:
bool: True if exist bool: True if exist
""" """
user_obj = self.get_Client(uidornickname=uidornikname) user_obj = self.get_Client(uidornickname=uidornickname)
if user_obj is None: if user_obj is None:
return False return False
@@ -231,9 +235,9 @@ class Client:
""" """
pattern = fr'[:|@|%|\+|~|\*]*' pattern = fr'[:|@|%|\+|~|\*]*'
parsed_UID = sub(pattern, '', uid) parsed_uid = sub(pattern, '', uid)
if not parsed_UID: if not parsed_uid:
return None return None
return parsed_UID return parsed_uid

View File

@@ -9,6 +9,10 @@ class Command:
DB_COMMANDS: list['MCommand'] = [] DB_COMMANDS: list['MCommand'] = []
def __init__(self, loader: 'Loader'): def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader instance.
"""
self.Loader = loader self.Loader = loader
self.Base = loader.Base self.Base = loader.Base
self.Logs = loader.Logs self.Logs = loader.Logs
@@ -32,7 +36,7 @@ class Command:
def get_command(self, command_name: str, module_name: str) -> Optional[MCommand]: def get_command(self, command_name: str, module_name: str) -> Optional[MCommand]:
for command in self.DB_COMMANDS: for command in self.DB_COMMANDS:
if command.command_name.lower() == command_name and command.module_name == module_name: if command.command_name.lower() == command_name.lower() and command.module_name.lower() == module_name.lower():
return command return command
return None return None
@@ -86,7 +90,7 @@ class Command:
admin_level = admin.level if admin else 0 admin_level = admin.level if admin else 0
commands = self.get_commands_by_level(admin_level) commands = self.get_commands_by_level(admin_level)
if command_name in [command.command_name for command in commands]: if command_name.lower() in [command.command_name.lower() for command in commands]:
return True return True
return False return False

View File

@@ -1,3 +1,5 @@
import sys
import yaml
from json import load from json import load
from sys import exit from sys import exit
from os import sep from os import sep
@@ -13,49 +15,43 @@ class Configuration:
self.Loader = loader self.Loader = loader
self.Logs = loader.Logs self.Logs = loader.Logs
self._config_model: MConfig = self.__load_service_configuration() self.configuration_model = self.__load_service_configuration()
loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL) loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL) loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD) loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD)
return None return None
def get_config_model(self) -> MConfig: @property
def configuration_model(self) -> MConfig:
return self._config_model return self._config_model
def __load_json_service_configuration(self) -> Optional[dict[str, Any]]: @configuration_model.setter
def configuration_model(self, conf_model: MConfig):
self._config_model = conf_model
def __load_config_file(self) -> Optional[dict[str, Any]]:
try: try:
conf_filename = f'config{sep}configuration.json' conf_filename = f'config{sep}configuration.yaml'
with open(conf_filename, 'r') as configuration_data: with open(conf_filename, 'r') as conf:
configuration: dict[str, Union[str, int, list, dict]] = load(configuration_data) configuration: dict[str, dict[str, Any]] = yaml.safe_load(conf)
return configuration
return configuration.get('configuration', None)
except FileNotFoundError as fe: except FileNotFoundError as fe:
self.Logs.error(f'FileNotFound: {fe}') self.Logs.error(f'FileNotFound: {fe}')
self.Logs.error('Configuration file not found please create config/configuration.json') self.Logs.error('Configuration file not found please create config/configuration.yaml')
exit(0) exit("Configuration file not found please create config/configuration.yaml")
except KeyError as ke:
self.Logs.error(f'Key Error: {ke}')
self.Logs.error('The key must be defined in core/configuration.json')
def __load_service_configuration(self) -> MConfig: def __load_service_configuration(self) -> MConfig:
try: try:
import_config = self.__load_json_service_configuration() import_config = self.__load_config_file()
if import_config is None:
self.Logs.error("Error While importing configuration file!", exc_info=True)
raise Exception("Error While importing yaml configuration")
Model_keys = MConfig().to_dict() list_key_to_remove: list[str] = [key_to_del for key_to_del in import_config if key_to_del not in MConfig().get_attributes()]
model_key_list: list = [] for key_to_remove in list_key_to_remove:
json_config_key_list: list = [] import_config.pop(key_to_remove, None)
self.Logs.warning(f"[!] The key {key_to_remove} is not expected, it has been removed from the system ! please remove it from configuration.json file [!]")
for key in Model_keys:
model_key_list.append(key)
for key in import_config:
json_config_key_list.append(key)
for json_conf in json_config_key_list:
if not json_conf in model_key_list:
import_config.pop(json_conf, None)
self.Logs.warning(f"[!] The key {json_conf} is not expected, it has been removed from the system ! please remove it from configuration.json file [!]")
self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!") self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!")
return MConfig(**import_config) return MConfig(**import_config)

View File

@@ -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)

View File

@@ -0,0 +1,54 @@
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'):
"""Init method
Args:
loader (Loader): The loader Object
"""
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.')
return None
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()

View File

@@ -0,0 +1,33 @@
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'):
"""ProtocolFactory init.
Args:
uplink (Irc): The Irc object
"""
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!")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,547 @@
from abc import ABC, abstractmethod
from typing import Optional, TYPE_CHECKING
from core.classes.protocols.command_handler import CommandHandler
if TYPE_CHECKING:
from core.definition import MClient, MSasl
class IProtocol(ABC):
Handler: Optional[CommandHandler] = None
@abstractmethod
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.
Args:
message (str): contient la commande à envoyer au serveur.
print_log (bool): If True then print logs
"""
@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:
"""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.
"""
@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_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_
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:
nick_to_sapart (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_obj (MClient): The Client UID
"""
@abstractmethod
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:
uid (str): The UID or the Nickname
reason (str): The reason for the quit
print_log (bool): If True then print logs
"""
@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:
"""Send raw message to the server
Args:
raw_command (str): The raw command you want to send.
"""
# ------------------------------------------------------------------------
# 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:
"""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]) -> Optional['MSasl']:
"""Handle SASL coming from a server
Args:
serverMsg (list[str]): Original server message
Returns:
"""
@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
[':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...']
Args:
serverMsg (list[str]): The server reply
"""
@abstractmethod
def on_kick(self, serverMsg: list[str]) -> None:
"""When a user is kicked out from a channel
Eg. ['@unrealircd.org...', ':001', 'KICK', '#jsonrpc', '001ELW13T', ':Kicked', 'from', 'JSONRPC', 'User']
Args:
serverMsg (list[str]): The server message
"""

View File

@@ -1,15 +1,19 @@
from base64 import b64decode from base64 import b64decode
from re import match, findall, search from re import match, findall, search
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Any, Optional
from ssl import SSLEOFError, SSLError 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: if TYPE_CHECKING:
from core.irc import Irc from core.irc import Irc
from core.classes.sasl import Sasl from core.classes.sasl import Sasl
from core.definition import MClient, MSasl from core.definition import MClient, MSasl
class Unrealircd6: class Unrealircd6(IProtocol):
def __init__(self, ircInstance: 'Irc'): def __init__(self, ircInstance: 'Irc'):
self.name = 'UnrealIRCD-6' self.name = 'UnrealIRCD-6'
@@ -25,17 +29,20 @@ class Unrealircd6:
self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT',
'EOS', 'PRIVMSG', 'MODE', 'UMODE2', 'EOS', 'PRIVMSG', 'MODE', 'UMODE2',
'VERSION', 'REPUTATION', 'SVS2MODE', 'VERSION', 'REPUTATION', 'SVS2MODE',
'SLOG', 'NICK', 'PART', 'PONG', 'SASL', 'SLOG', 'NICK', 'PART', 'PONG', 'SASL', 'PING',
'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO', 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO',
'006', '007', '018'} '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 """Get the position of known commands
Args: Args:
cmd (list[str]): The server response cmd (list[str]): The server response
log (bool): If true it will log in the logger
Returns: Returns:
tuple[int, Optional[str]]: The position and the command. tuple[int, Optional[str]]: The position and the command.
@@ -44,10 +51,35 @@ class Unrealircd6:
if token.upper() in self.known_protocol: if token.upper() in self.known_protocol:
return index, token.upper() 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) 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))
self.Handler.register(m(command_name="KICK", func=self.on_kick))
return None
def parse_server_msg(self, server_msg: list[str]) -> Optional[str]: def parse_server_msg(self, server_msg: list[str]) -> Optional[str]:
"""Parse the server message and return the command """Parse the server message and return the command
@@ -171,42 +203,52 @@ class Unrealircd6:
"""Créer le link et envoyer les informations nécessaires pour la """Créer le link et envoyer les informations nécessaires pour la
connexion au serveur. connexion au serveur.
""" """
service_id = self.__Config.SERVICE_ID
nickname = self.__Config.SERVICE_NICKNAME service_nickname = self.__Config.SERVICE_NICKNAME
username = self.__Config.SERVICE_USERNAME service_username = self.__Config.SERVICE_USERNAME
realname = self.__Config.SERVICE_REALNAME service_realname = self.__Config.SERVICE_REALNAME
chan = self.__Config.SERVICE_CHANLOG service_channel_log = self.__Config.SERVICE_CHANLOG
info = self.__Config.SERVICE_INFO service_info = self.__Config.SERVICE_INFO
smodes = self.__Config.SERVICE_SMODES service_smodes = self.__Config.SERVICE_SMODES
cmodes = self.__Config.SERVICE_CMODES service_cmodes = self.__Config.SERVICE_CMODES
umodes = self.__Config.SERVICE_UMODES service_umodes = self.__Config.SERVICE_UMODES
host = self.__Config.SERVICE_HOST service_hostname = self.__Config.SERVICE_HOST
service_name = self.__Config.SERVICE_NAME service_name = self.__Config.SERVICE_NAME
protocolversion = self.protocol_version protocolversion = self.protocol_version
password = self.__Config.SERVEUR_PASSWORD server_password = self.__Config.SERVEUR_PASSWORD
link = self.__Config.SERVEUR_LINK server_link = self.__Config.SERVEUR_LINK
server_id = self.__Config.SERVEUR_ID server_id = self.__Config.SERVEUR_ID
service_id = self.__Config.SERVICE_ID
version = self.__Config.CURRENT_VERSION version = self.__Config.CURRENT_VERSION
unixtime = self.__Utils.get_unixtime() 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 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 SID={server_id}")
self.send2socket(f":{server_id} PROTOCTL BOOTED={unixtime}") 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(f":{server_id} {nickname} :Reserved for services") self.send2socket("EOS")
self.send2socket(f":{server_id} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * fwAAAQ== :{realname}") self.send2socket(f":{server_id} {service_nickname} :Reserved for services")
self.send_sjoin(chan) self.send2socket(f":{server_id} UID {service_nickname} 1 {unixtime} {service_username} {service_hostname} {service_id} * {service_smodes} * * fwAAAQ== :{service_realname}")
self.send2socket(f":{server_id} TKL + Q * {nickname} {host} 0 {unixtime} :Reserved for services") self.send_sjoin(service_channel_log)
self.send2socket(f":{service_id} MODE {chan} {cmodes}") 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') 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: 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 # 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}") self.send2socket(f":{self.__Config.SERVEUR_ID} TKL + G {nickname} {hostname} {set_by} {expire_timestamp} {set_at_timestamp} :{reason}")
@@ -225,6 +267,42 @@ class Unrealircd6:
self.__Irc.User.update_nickname(userObj.uid, newnickname) self.__Irc.User.update_nickname(userObj.uid, newnickname)
return None 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: def send_squit(self, server_id: str, server_link: str, reason: str) -> None:
if not reason: if not reason:
@@ -427,7 +505,7 @@ class Unrealircd6:
reason (str): The reason for the quit reason (str): The reason for the quit
""" """
user_obj = self.__Irc.User.get_user(uidornickname=uid) user_obj = self.__Irc.User.get_user(uidornickname=uid)
reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_reputation(uidornickname=uid)
if not user_obj is None: if not user_obj is None:
self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log) self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log)
@@ -561,12 +639,112 @@ class Unrealircd6:
return None 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: The response as dictionary.
>>> {"uid": "", "newnickname": "", "timestamp": ""}
"""
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 # # HANDLE EVENTS #
##################### #####################
def on_svs2mode(self, serverMsg: list[str]) -> None: def on_svs2mode(self, serverMsg: list[str]) -> None:
"""Handle svs2mode coming from a server """Handle svs2mode coming from a server
>>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r']
Args: Args:
serverMsg (list[str]): Original server message serverMsg (list[str]): Original server message
@@ -604,6 +782,7 @@ class Unrealircd6:
def on_umode2(self, serverMsg: list[str]) -> None: def on_umode2(self, serverMsg: list[str]) -> None:
"""Handle umode2 coming from a server """Handle umode2 coming from a server
>>> [':adator_', 'UMODE2', '-i']
Args: Args:
serverMsg (list[str]): Original server message serverMsg (list[str]): Original server message
@@ -730,6 +909,7 @@ class Unrealircd6:
self.__Irc.User.update_nickname(uid, newnickname) self.__Irc.User.update_nickname(uid, newnickname)
self.__Irc.Client.update_nickname(uid, newnickname) self.__Irc.Client.update_nickname(uid, newnickname)
self.__Irc.Admin.update_nickname(uid, newnickname) self.__Irc.Admin.update_nickname(uid, newnickname)
self.__Irc.Reputation.update(uid, newnickname)
return None return None
@@ -850,6 +1030,8 @@ class Unrealircd6:
self.__Logs.info(f"# VERSION : {version} ") self.__Logs.info(f"# VERSION : {version} ")
self.__Logs.info(f"################################################") self.__Logs.info(f"################################################")
self.send_sjoin(self.__Config.SERVICE_CHANLOG)
if self.__Base.check_for_new_version(False): if self.__Base.check_for_new_version(False):
self.send_priv_msg( self.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME, nick_from=self.__Config.SERVICE_NICKNAME,
@@ -860,7 +1042,7 @@ class Unrealircd6:
# Initialisation terminé aprés le premier PING # Initialisation terminé aprés le premier PING
self.send_priv_msg( self.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME, nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[{self.__Config.COLORS.green}INFORMATION{self.__Config.COLORS.nogc}] >> Defender is ready", msg=tr("[ %sINFORMATION%s ] >> %s is ready!", self.__Config.COLORS.green, self.__Config.COLORS.nogc, self.__Config.SERVICE_NICKNAME),
channel=self.__Config.SERVICE_CHANLOG channel=self.__Config.SERVICE_CHANLOG
) )
self.__Config.DEFENDER_INIT = 0 self.__Config.DEFENDER_INIT = 0
@@ -869,6 +1051,10 @@ class Unrealircd6:
for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy(): for module in self.__Irc.ModuleUtils.model_get_loaded_modules().copy():
module.class_instance.cmd(server_msg_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 return None
except IndexError as ie: except IndexError as ie:
self.__Logs.error(f"{__name__} - Key Error: {ie}") self.__Logs.error(f"{__name__} - Key Error: {ie}")
@@ -946,6 +1132,11 @@ class Unrealircd6:
fp_match = match(pattern, serverMsg[0]) fp_match = match(pattern, serverMsg[0])
fingerprint = fp_match.group(1) if fp_match else None fingerprint = fp_match.group(1) if fp_match else None
# Extract tls_cipher information
pattern = r'^.*tls_cipher=([^;]+).*$'
tlsc_match = match(pattern, serverMsg[0])
tls_cipher = tlsc_match.group(1) if tlsc_match else None
if geoip_match: if geoip_match:
geoip = geoip_match.group(1) geoip = geoip_match.group(1)
else: else:
@@ -963,6 +1154,7 @@ class Unrealircd6:
umodes=umodes, umodes=umodes,
vhost=vhost, vhost=vhost,
fingerprint=fingerprint, fingerprint=fingerprint,
tls_cipher=tls_cipher,
isWebirc=isWebirc, isWebirc=isWebirc,
isWebsocket=isWebsocket, isWebsocket=isWebsocket,
remote_ip=remote_ip, remote_ip=remote_ip,
@@ -971,6 +1163,49 @@ class Unrealircd6:
connexion_datetime=datetime.now() connexion_datetime=datetime.now()
) )
) )
# Auto Auth admin via fingerprint
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("[ %sFINGERPRINT 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 return None
except IndexError as ie: except IndexError as ie:
self.__Logs.error(f"{__name__} - Index Error: {ie}") self.__Logs.error(f"{__name__} - Index Error: {ie}")
@@ -1010,15 +1245,6 @@ class Unrealircd6:
) )
return None 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(':','') cmd_to_send = convert_to_string.replace(':','')
self.__Base.log_cmd(user_trigger, cmd_to_send) self.__Base.log_cmd(user_trigger, cmd_to_send)
@@ -1072,7 +1298,7 @@ class Unrealircd6:
except AttributeError as ae: except AttributeError as ae:
self.__Logs.error(f"Attribute Error: {ae}") self.__Logs.error(f"Attribute Error: {ae}")
except Exception as err: 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: def on_server_ping(self, serverMsg: list[str]) -> None:
"""Send a PONG message to the server """Send a PONG message to the server
@@ -1081,7 +1307,6 @@ class Unrealircd6:
serverMsg (list[str]): List of str coming from the server serverMsg (list[str]): List of str coming from the server
""" """
try: try:
#
pong = str(serverMsg[1]).replace(':','') pong = str(serverMsg[1]).replace(':','')
self.send2socket(f"PONG :{pong}", print_log=False) self.send2socket(f"PONG :{pong}", print_log=False)
@@ -1090,6 +1315,11 @@ class Unrealircd6:
self.__Logs.error(f"{__name__} - General Error: {err}") self.__Logs.error(f"{__name__} - General Error: {err}")
def on_server(self, serverMsg: list[str]) -> None: def on_server(self, serverMsg: list[str]) -> None:
"""_summary_
Args:
serverMsg (list[str]): _description_
"""
try: try:
# ['SERVER', 'irc.local.org', '1', ':U6100-Fhn6OoE-001', 'Local', 'Server'] # ['SERVER', 'irc.local.org', '1', ':U6100-Fhn6OoE-001', 'Local', 'Server']
sCopy = serverMsg.copy() sCopy = serverMsg.copy()
@@ -1152,7 +1382,7 @@ class Unrealircd6:
Args: Args:
serverMsg (list[str]): List of str coming from the server 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 # Réponse a un CTCP VERSION
try: try:
@@ -1161,6 +1391,7 @@ class Unrealircd6:
arg = serverMsg[4].replace(':', '') arg = serverMsg[4].replace(':', '')
if nickname is None: if nickname is None:
self.__Logs.debug(serverMsg)
return None return None
if arg == '\x01PING': if arg == '\x01PING':
@@ -1174,6 +1405,7 @@ class Unrealircd6:
nick_to=nickname, nick_to=nickname,
msg=f"\x01PING {ping_response} secs\x01" msg=f"\x01PING {ping_response} secs\x01"
) )
self.__Logs.debug(serverMsg)
return None return None
except Exception as err: except Exception as err:
@@ -1230,7 +1462,7 @@ class Unrealircd6:
except Exception as err: except Exception as err:
self.__Logs.error(f'General Error: {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 """Handle SASL coming from a server
Args: Args:
@@ -1243,7 +1475,7 @@ class Unrealircd6:
# [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '0014ZZH1F', 'S', 'EXTERNAL', 'zzzzzzzkey'] # [':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', 'C', 'sasakey==']
# [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '00157Z26U', 'D', 'A'] # [':irc.local.org', 'SASL', 'defender-dev.deb.biz.st', '00157Z26U', 'D', 'A']
psasl = self.__Irc.Sasl
sasl_enabled = False sasl_enabled = False
for smod in self.__Settings.SMOD_MODULES: for smod in self.__Settings.SMOD_MODULES:
if smod.name == 'sasl': if smod.name == 'sasl':
@@ -1283,6 +1515,7 @@ class Unrealircd6:
sasl_obj.fingerprint = str(sCopy[6]) 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.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 return sasl_obj
case 'C': case 'C':
@@ -1295,10 +1528,105 @@ class Unrealircd6:
sasl_obj.username = username sasl_obj.username = username
sasl_obj.password = password sasl_obj.password = password
self.on_sasl_authentication_process(sasl_obj)
return sasl_obj return sasl_obj
elif sasl_obj.mechanisme == "EXTERNAL": elif sasl_obj.mechanisme == "EXTERNAL":
sasl_obj.message_type = sasl_message_type sasl_obj.message_type = sasl_message_type
self.on_sasl_authentication_process(sasl_obj)
return sasl_obj return sasl_obj
except Exception as err: except Exception as err:
self.__Logs.error(f'General Error: {err}', exc_info=True) 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 <nick> :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...']
Args:
serverMsg (list[str]): The server reply
"""
try:
scopy = serverMsg.copy()
available_vars = ['creationtime', 'certfp', 'tls_cipher']
uid = str(scopy[3])
var = str(scopy[4]).lower()
value = str(scopy[5]).replace(':', '')
user_obj = self.__Irc.User.get_user(uid)
if user_obj is None:
return None
match var:
case 'certfp':
user_obj.fingerprint = value
case 'tls_cipher':
user_obj.tls_cipher = value
case _:
return None
...
except Exception as e:
self.__Logs.error(f"General Error: {e}")
def on_kick(self, serverMsg: list[str]) -> None:
"""When a user is kicked out from a channel
['@unrealircd.org/issued-by=RPC:admin-for-test@...', ':001', 'KICK', '#jsonrpc', '001ELW13T', ':Kicked', 'from', 'JSONRPC', 'User']
Args:
serverMsg (list[str]): The server message
"""
scopy = serverMsg.copy()
uid = scopy[4]
channel = scopy[3]
# Delete the user from the channel.
self.__Irc.Channel.delete_user_from_channel(channel, uid)
return None

View File

@@ -3,7 +3,6 @@ import sys
import time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import socket import socket
from core.classes.protocol import Protocol
if TYPE_CHECKING: if TYPE_CHECKING:
from core.irc import Irc from core.irc import Irc
@@ -15,14 +14,20 @@ REHASH_MODULES = [
'core.classes.config', 'core.classes.config',
'core.base', 'core.base',
'core.classes.commands', 'core.classes.commands',
'core.classes.protocols.interface',
'core.classes.protocols.factory',
'core.classes.protocols.unreal6', 'core.classes.protocols.unreal6',
'core.classes.protocols.inspircd', 'core.classes.protocols.inspircd'
'core.classes.protocol'
] ]
def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -> None: 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. # reload modules.
for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
uplink.ModuleUtils.unload_one_module(uplink, module.module_name) uplink.ModuleUtils.unload_one_module(uplink, module.module_name)
@@ -33,14 +38,8 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -
uplink.Client.CLIENT_DB.clear() # Clear Client object uplink.Client.CLIENT_DB.clear() # Clear Client object
uplink.Base.garbage_collector_thread() uplink.Base.garbage_collector_thread()
# Reload configuration
uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model()
uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader)
uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol
uplink.Logs.debug(f'[{uplink.Config.SERVICE_NICKNAME} RESTART]: Reloading configuration!') uplink.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=reason)
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.Logs.debug('Restarting Defender ...')
uplink.IrcSocket.shutdown(socket.SHUT_RDWR) uplink.IrcSocket.shutdown(socket.SHUT_RDWR)
uplink.IrcSocket.close() uplink.IrcSocket.close()
@@ -49,11 +48,19 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -
time.sleep(0.5) time.sleep(0.5)
uplink.Logs.warning('-- Waiting for socket to close ...') uplink.Logs.warning('-- Waiting for socket to close ...')
# Reload configuration
uplink.Loader.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).configuration_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.init_service_user()
uplink.Utils.create_socket(uplink) uplink.Utils.create_socket(uplink)
uplink.Protocol.send_link() uplink.Protocol.send_link()
uplink.join_saved_channels()
uplink.ModuleUtils.db_load_all_existing_modules(uplink)
uplink.Config.DEFENDER_RESTART = 0 uplink.Config.DEFENDER_RESTART = 0
def rehash_service(uplink: 'Irc', nickname: str) -> None: def rehash_service(uplink: 'Irc', nickname: str) -> None:
@@ -69,8 +76,8 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None:
msg=f'[REHASH] Module [{mod}] reloaded', msg=f'[REHASH] Module [{mod}] reloaded',
channel=uplink.Config.SERVICE_CHANLOG channel=uplink.Config.SERVICE_CHANLOG
) )
uplink.Utils = sys.modules['core.utils']
uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model() uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).configuration_model
uplink.Config.HSID = config_model_bakcup.HSID uplink.Config.HSID = config_model_bakcup.HSID
uplink.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT uplink.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT
uplink.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART uplink.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART
@@ -105,8 +112,9 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None:
uplink.Commands = uplink.Loader.CommandModule.Command(uplink.Loader) uplink.Commands = uplink.Loader.CommandModule.Command(uplink.Loader)
uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('db_commands') 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 = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol uplink.Protocol = uplink.Loader.PFactory.get()
uplink.Protocol.register_command()
# 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():

View File

@@ -9,9 +9,14 @@ class Reputation:
UID_REPUTATION_DB: list[MReputation] = [] UID_REPUTATION_DB: list[MReputation] = []
def __init__(self, loader: 'Loader'): def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader instance.
"""
self.Logs = loader.Logs self.Logs = loader.Logs
self.MReputation: MReputation = MReputation self.MReputation: Optional[MReputation] = None
def insert(self, new_reputation_user: MReputation) -> bool: def insert(self, new_reputation_user: MReputation) -> bool:
"""Insert a new Reputation User object """Insert a new Reputation User object
@@ -47,13 +52,13 @@ class Reputation:
Args: Args:
uid (str): UID of the user uid (str): UID of the user
newNickname (str): New nickname new_nickname (str): New nickname
Returns: Returns:
bool: True if updated bool: True if updated
""" """
reputation_obj = self.get_Reputation(uid) reputation_obj = self.get_reputation(uid)
if reputation_obj is None: if reputation_obj is None:
return False return False
@@ -89,7 +94,7 @@ class Reputation:
return result return result
def get_Reputation(self, uidornickname: str) -> Optional[MReputation]: def get_reputation(self, uidornickname: str) -> Optional[MReputation]:
"""Get The User Object model """Get The User Object model
Args: Args:
@@ -116,7 +121,7 @@ class Reputation:
str|None: Return the UID str|None: Return the UID
""" """
reputation_obj = self.get_Reputation(uidornickname) reputation_obj = self.get_reputation(uidornickname)
if reputation_obj is None: if reputation_obj is None:
return None return None
@@ -132,7 +137,7 @@ class Reputation:
Returns: Returns:
str|None: the nickname str|None: the nickname
""" """
reputation_obj = self.get_Reputation(uidornickname) reputation_obj = self.get_reputation(uidornickname)
if reputation_obj is None: if reputation_obj is None:
return None return None
@@ -149,7 +154,7 @@ class Reputation:
bool: True if exist bool: True if exist
""" """
reputation_obj = self.get_Reputation(uidornickname) reputation_obj = self.get_reputation(uidornickname)
if isinstance(reputation_obj, MReputation): if isinstance(reputation_obj, MReputation):
return True return True

View File

@@ -1,4 +1,4 @@
from typing import Optional, Union, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MSasl from core.definition import MSasl
@@ -9,13 +9,18 @@ class Sasl:
DB_SASL: list['MSasl'] = [] DB_SASL: list['MSasl'] = []
def __init__(self, loader: 'Loader'): def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader instance.
"""
self.Logs = loader.Logs # logger self.Logs = loader.Logs # logger
def insert_sasl_client(self, psasl: 'MSasl') -> bool: def insert_sasl_client(self, psasl: 'MSasl') -> bool:
"""Insert a new Sasl authentication """Insert a new Sasl authentication
Args: Args:
new_user (UserModel): New userModel object psasl (MSasl): New userModel object
Returns: Returns:
bool: True if inserted bool: True if inserted
@@ -38,7 +43,7 @@ class Sasl:
"""Delete the User starting from the UID """Delete the User starting from the UID
Args: Args:
uid (str): UID of the user client_uid (str): UID of the user
Returns: Returns:
bool: True if deleted bool: True if deleted

View File

@@ -1,10 +1,10 @@
'''This class should never be reloaded. """This class should never be reloaded.
''' """
from logging import Logger from logging import Logger
from threading import Timer, Thread, RLock from threading import Timer, Thread, RLock
from socket import socket from socket import socket
from typing import Any, Optional, TYPE_CHECKING from typing import Any, Optional, TYPE_CHECKING
from core.definition import MSModule from core.definition import MSModule, MAdmin
if TYPE_CHECKING: if TYPE_CHECKING:
from core.classes.user import User from core.classes.user import User
@@ -15,33 +15,40 @@ class Settings:
the whole life of the app the whole life of the app
""" """
RUNNING_TIMERS: list[Timer] = [] RUNNING_TIMERS: list[Timer] = []
RUNNING_THREADS: list[Thread] = [] RUNNING_THREADS: list[Thread] = []
RUNNING_SOCKETS: list[socket] = [] RUNNING_SOCKETS: list[socket] = []
PERIODIC_FUNC: dict[object] = {} PERIODIC_FUNC: dict[str, Any] = {}
LOCK: RLock = RLock() LOCK: RLock = RLock()
CONSOLE: bool = False CONSOLE: bool = False
MAIN_SERVER_HOSTNAME: str = None MAIN_SERVER_HOSTNAME: str = None
PROTOCTL_USER_MODES: list[str] = [] MAIN_SERVER_ID: str = None
PROTOCTL_PREFIX: list[str] = [] 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] = [] SMOD_MODULES: list[MSModule] = []
"""List contains all Server modules""" """List contains all Server modules"""
__CACHE: dict[str, Any] = {} __CACHE: dict[str, Any] = {}
"""Use set_cache or get_cache instead""" """Use set_cache or get_cache instead"""
__TRANSLATION: dict[str, list[list[str]]] = dict() __TRANSLATION: dict[str, list[list[str]]] = dict()
"""Translation Varibale""" """Translation Varibale"""
__LANG: str = "EN" __LANG: str = "EN"
__INSTANCE_OF_USER_UTILS: Optional['User'] = None __INSTANCE_OF_USER_UTILS: Optional['User'] = None
"""Instance of the User Utils class""" """Instance of the User Utils class"""
__LOGGER: Optional[Logger] = None __CURRENT_ADMIN: Optional['MAdmin'] = None
"""The Current Admin Object Model"""
__LOGGER: Optional[Logger] = None
"""Instance of the logger""" """Instance of the logger"""
def set_cache(self, key: str, value_to_cache: Any): def set_cache(self, key: str, value_to_cache: Any):
@@ -75,6 +82,7 @@ class Settings:
@property @property
def global_translation(self) -> dict[str, list[list[str]]]: def global_translation(self) -> dict[str, list[list[str]]]:
"""Get/set global translation variable"""
return self.__TRANSLATION return self.__TRANSLATION
@global_translation.setter @global_translation.setter
@@ -83,6 +91,7 @@ class Settings:
@property @property
def global_lang(self) -> str: def global_lang(self) -> str:
"""Global default language."""
return self.__LANG return self.__LANG
@global_lang.setter @global_lang.setter
@@ -97,8 +106,18 @@ class Settings:
def global_user(self, user_utils_instance: 'User') -> None: def global_user(self, user_utils_instance: 'User') -> None:
self.__INSTANCE_OF_USER_UTILS = user_utils_instance self.__INSTANCE_OF_USER_UTILS = user_utils_instance
@property
def current_admin(self) -> MAdmin:
"""Current admin data model."""
return self.__CURRENT_ADMIN
@current_admin.setter
def current_admin(self, current_admin: MAdmin) -> None:
self.__CURRENT_ADMIN = current_admin
@property @property
def global_logger(self) -> Logger: def global_logger(self) -> Logger:
"""Global logger Instance"""
return self.__LOGGER return self.__LOGGER
@global_logger.setter @global_logger.setter

View File

@@ -11,9 +11,13 @@ if TYPE_CHECKING:
class Translation: class Translation:
def __init__(self, loader: 'Loader') -> None: def __init__(self, loader: 'Loader') -> None:
"""
Args:
loader (Loader): The Loader instance.
"""
self.Logs = loader.Logs self.Logs = loader.Logs
self.Settings = loader.Settings self.Settings = loader.Settings
return None
def get_translation(self) -> dict[str, list[list[str]]]: def get_translation(self) -> dict[str, list[list[str]]]:
try: try:

View File

@@ -55,7 +55,7 @@ class User:
return False return False
user_obj.nickname = new_nickname user_obj.nickname = new_nickname
self.Logs.debug(f"UID ({uid}) has benn update with new nickname ({new_nickname}).")
return True return True
def update_mode(self, uidornickname: str, modes: str) -> bool: def update_mode(self, uidornickname: str, modes: str) -> bool:

View File

@@ -31,6 +31,7 @@ class MClient(MainModel):
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
@@ -50,6 +51,7 @@ class MUser(MainModel):
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
@@ -70,12 +72,14 @@ class MAdmin(MainModel):
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
score_connexion: int = 0 score_connexion: int = 0
geoip: str = None geoip: str = None
connexion_datetime: datetime = field(default=datetime.now()) connexion_datetime: datetime = field(default=datetime.now())
language: str = "EN"
level: int = 0 level: int = 0
@dataclass @dataclass
@@ -89,6 +93,7 @@ class MReputation(MainModel):
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
@@ -362,5 +367,16 @@ class MSasl(MainModel):
username: Optional[str] = None username: Optional[str] = None
password: Optional[str] = None password: Optional[str] = None
fingerprint: Optional[str] = None fingerprint: Optional[str] = None
language: str = "EN"
auth_success: bool = False auth_success: bool = False
level: int = 0 level: int = 0
@dataclass
class MRegister:
command_name: str
func: Any
@dataclass
class MIrcdCommand:
command_name: str
func: Any

150
core/install.py Normal file
View File

@@ -0,0 +1,150 @@
import argparse
import os
import sys
import json
from dataclasses import dataclass
from subprocess import check_call, CalledProcessError, check_output
from pathlib import Path
from platform import python_version_tuple
import traceback
parser = argparse.ArgumentParser(description="Python Installation Code")
parser.add_argument('--check-version', action='store_true', help='Check if the python version is ok!')
parser.add_argument('--install', action='store_true', help='Run the installation')
parser.add_argument('--git-update', action='store_true', help='Update from git (main repository)')
args = parser.parse_args()
PYTHON_REQUIRED_VERSION = (3, 10, 0)
PYTHON_SYSTEM_VERSION = tuple(map(int, python_version_tuple()))
ROOT_PATH = os.getcwd()
PYENV = Path(ROOT_PATH).joinpath('.pyenv/bin/python') if os.name != 'nt' else Path(ROOT_PATH).joinpath('.pyenv/Scripts/python.exe')
PIPENV = Path(f'{ROOT_PATH}/.pyenv/bin/pip') if os.name != 'nt' else Path(f'{ROOT_PATH}/.pyenv/Scripts/pip.exe')
USER_HOME_DIRECTORY = Path.home()
SYSTEMD_PATH = Path(USER_HOME_DIRECTORY).joinpath('.config', 'systemd', 'user')
PY_EXEC = 'defender.py'
SERVICE_FILE_NAME = 'defender.service'
@dataclass
class Package:
name: str = None
version: str = None
def __load_required_package_versions() -> list[Package]:
"""This will create Package model with package names and required version
"""
try:
DB_PACKAGES: list[Package] = []
version_filename = Path(ROOT_PATH).joinpath('version.json') # f'.{os.sep}version.json'
with open(version_filename, 'r') as version_data:
package_info:dict[str, str] = json.load(version_data)
for name, version in package_info.items():
if name == 'version':
continue
DB_PACKAGES.append(
Package(name=name, version=version)
)
return DB_PACKAGES
except FileNotFoundError as fe:
print(f"File not found: {fe}")
except Exception as err:
print(f"General Error: {err}")
def update_packages() -> None:
try:
newVersion = False
db_packages = __load_required_package_versions()
print(ROOT_PATH)
if sys.prefix not in PYENV.__str__():
print(f"You are probably running a new installation or you are not using your virtual env {PYENV}")
return newVersion
print(f"> Checking for dependencies versions ==> WAIT")
for package in db_packages:
newVersion = False
_required_version = package.version
_installed_version: str = None
output = check_output([PIPENV, 'show', package.name])
for line in output.decode().splitlines():
if line.startswith('Version:'):
_installed_version = line.split(':')[1].strip()
break
required_version = tuple(map(int, _required_version.split('.')))
installed_version = tuple(map(int, _installed_version.split('.')))
if required_version > installed_version:
print(f'> New version of {package.name} is available {installed_version} ==> {required_version}')
newVersion = True
if newVersion:
check_call([PIPENV, 'install', '--upgrade', package.name])
print(f"> Dependencies versions ==> OK")
return newVersion
except CalledProcessError:
print(f"[!] Package {package.name} not installed [!]")
except Exception as err:
print(f"UpdatePackage Error: {err}")
traceback.print_exc()
def run_git_update() -> None:
check_call(['git', 'pull', 'origin', 'main'])
def check_python_requirement():
if PYTHON_SYSTEM_VERSION < PYTHON_REQUIRED_VERSION:
raise RuntimeError(f"Your Python Version is not meeting the requirement, System Version: {PYTHON_SYSTEM_VERSION} < Required Version {PYTHON_REQUIRED_VERSION}")
def create_service_file():
pyenv = PYENV
systemd_path = SYSTEMD_PATH
py_exec = PY_EXEC
service_file_name = SERVICE_FILE_NAME
if not Path(systemd_path).exists():
print("[!] Folder not available")
sys.exit(1)
contain = f'''[Unit]
Description=Defender IRC Service
[Service]
ExecStart={pyenv} {py_exec}
WorkingDirectory={ROOT_PATH}
SyslogIdentifier=Defender
Restart=on-failure
[Install]
WantedBy=default.target
'''
with open(Path(systemd_path).joinpath(service_file_name), "w") as file:
file.write(contain)
print('Service file generated with current configuration')
print('Running IRC Service ...')
print(f"#"*24)
print("Installation complete ...")
print("If the configuration is correct, then you must see your service connected to your irc server")
print(f"If any issue, you can see the log file for debug {ROOT_PATH}{os.sep}logs{os.sep}defender.log")
print(f"#"*24)
def main():
if args.check_version:
check_python_requirement()
sys.exit(0)
if args.install:
create_service_file()
sys.exit(0)
if args.git_update:
run_git_update()
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1,314 +0,0 @@
import os
import json
from sys import exit, prefix
from dataclasses import dataclass
from subprocess import check_call, run, CalledProcessError, PIPE, check_output
from platform import python_version, python_version_tuple
class Install:
@dataclass
class CoreConfig:
install_log_file: str
unix_systemd_folder: str
service_file_name: str
service_cmd_executable: list
service_cmd_daemon_reload: list
defender_main_executable: str
python_min_version: str
python_current_version_tuple: tuple[int, int, int]
python_current_version: tuple[int, int, int]
defender_install_folder: str
venv_folder: str
venv_cmd_installation: list
venv_cmd_requirements: list[str]
venv_pip_executable: str
venv_python_executable: str
@dataclass
class Package:
name: str = None
version: str = None
DB_PACKAGES: list[Package] = []
def __init__(self) -> None:
self.set_configuration()
if self.skip_install:
self.install_dependencies()
self.check_packages_version()
return None
self.check_packages_version()
# Sinon tester les dependances python et les installer avec pip
if self.do_install():
self.install_dependencies()
self.create_service_file()
self.print_final_message()
return None
def set_configuration(self):
self.skip_install = False
defender_install_folder = os.getcwd()
venv_folder = '.pyenv'
unix_user_home_directory = os.path.expanduser("~")
unix_systemd_folder = os.path.join(unix_user_home_directory, '.config', 'systemd', 'user')
defender_main_executable = os.path.join(defender_install_folder, 'defender.py')
self.config = self.CoreConfig(
install_log_file='install.log',
unix_systemd_folder=unix_systemd_folder,
service_file_name='defender.service',
service_cmd_executable=['systemctl', '--user', 'start', 'defender'],
service_cmd_daemon_reload=['systemctl', '--user', 'daemon-reload'],
defender_main_executable=defender_main_executable,
python_min_version=(3, 10, 0),
python_current_version_tuple=tuple(map(int, python_version_tuple())),
python_current_version=python_version(),
defender_install_folder=defender_install_folder,
venv_folder=venv_folder,
venv_cmd_installation=['python3', '-m', 'venv', venv_folder],
venv_cmd_requirements=['sqlalchemy','psutil','requests','faker','pyyaml','unrealircd_rpc_py'],
venv_pip_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}pip',
venv_python_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}python'
)
if not self.check_python_version():
# If the Python version is not good then Exit
exit("[!] Python version error [!]")
if not os.path.exists(os.path.join(self.config.defender_install_folder, 'config', 'configuration.json')):
# If configuration file do not exist
exit("[!] Configuration file (core/configuration.json) doesn't exist! please create it [!]")
# Exclude Windows OS from the installation
if os.name == 'nt':
# If windows, modify pip and python virtual environment executable
self.config.venv_pip_executable = f'{os.path.join(defender_install_folder, venv_folder, "Scripts")}{os.sep}pip.exe'
self.config.venv_python_executable = f'{os.path.join(defender_install_folder, venv_folder, "Scripts")}{os.sep}python.exe'
self.skip_install = True
return False
if self.is_root():
exit(f'[!] I highly not recommend running Defender as root [!]')
self.skip_install = True
return False
def is_root(self) -> bool:
if os.geteuid() != 0:
print('> User without privileges ==> OK')
return False
elif os.geteuid() == 0:
print('[!] Do not use root to install Defender [!]')
exit("Do not use root to install Defender")
return True
def do_install(self) -> bool:
full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name)
if not os.path.exists(full_service_file_path):
print(f'[!] Service file does not exist [!]')
return True
# Check if virtual env exist
if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'):
self.run_subprocess(self.config.venv_cmd_installation)
print(f'[!] Virtual env does not exist run the install [!]')
return True
def run_subprocess(self, command:list) -> None:
print(f'> {command}')
try:
check_call(command)
print("The command completed successfully.")
except CalledProcessError as e:
print(f"The command failed with the return code: {e.returncode}")
print(f"Try to install dependencies ...")
exit(5)
def get_packages_version_from_json(self) -> None:
"""This will create Package model with package names and required version
"""
try:
version_filename = f'.{os.sep}version.json'
with open(version_filename, 'r') as version_data:
package_info:dict[str, str] = json.load(version_data)
for name, version in package_info.items():
if name == 'version':
continue
self.DB_PACKAGES.append(
self.Package(name=name, version=version)
)
return None
except FileNotFoundError as fe:
print(f"File not found: {fe}")
except Exception as err:
print(f"General Error: {err}")
def check_packages_version(self) -> bool:
try:
newVersion = False
self.get_packages_version_from_json()
if not self.config.venv_folder in prefix:
print(f"You are probably running a new installation or you are not using your virtual env {self.config.venv_folder}")
return newVersion
print(f"> Checking for dependencies versions ==> WAIT")
for package in self.DB_PACKAGES:
newVersion = False
_required_version = package.version
_installed_version: str = None
output = check_output([self.config.venv_pip_executable, 'show', package.name])
for line in output.decode().splitlines():
if line.startswith('Version:'):
_installed_version = line.split(':')[1].strip()
break
required_version = tuple(map(int, _required_version.split('.')))
installed_version = tuple(map(int, _installed_version.split('.')))
if required_version > installed_version:
print(f'> New version of {package.name} is available {installed_version} ==> {required_version}')
newVersion = True
if newVersion:
self.run_subprocess([self.config.venv_pip_executable, 'install', '--upgrade', package.name])
print(f"> Dependencies versions ==> OK")
return newVersion
except CalledProcessError:
print(f"[!] Package {package.name} not installed [!]")
except Exception as err:
print(f"General Error: {err}")
def check_python_version(self) -> bool:
"""Test si la version de python est autorisée ou non
Returns:
bool: True si la version de python est autorisé sinon False
"""
if self.config.python_current_version_tuple < self.config.python_min_version:
print(f"## Your python version must be greather than or equal to {self.config.python_min_version} ##")
return False
print(f"> Version of python : {self.config.python_current_version} ==> OK")
return True
def check_package(self, package_name) -> bool:
try:
# Run a command in the virtual environment's Python to check if the package is installed
run([self.config.venv_python_executable, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE)
return True
except CalledProcessError as cpe:
print(cpe)
return False
def install_dependencies(self) -> None:
"""### Verifie les dépendances si elles sont installées
- Test si les modules sont installés
- Met a jour pip
- Install les modules manquants
"""
do_install = False
# Check if virtual env exist
if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'):
self.run_subprocess(self.config.venv_cmd_installation)
do_install = True
for module in self.config.venv_cmd_requirements:
module = module.replace('pyyaml', 'yaml')
if not self.check_package(module):
do_install = True
if not do_install:
return None
print("===> Clean pip cache")
self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge'])
print("===> Check if pip is up to date")
self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
if not self.check_package('greenlet'):
self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet'])
print('====> Greenlet installed')
for module in self.config.venv_cmd_requirements:
if not self.check_package(module):
print("### Trying to install missing python packages ###")
self.run_subprocess([self.config.venv_pip_executable, 'install', module])
print(f"====> Module {module} installed!")
else:
print(f"==> {module} already installed")
def create_service_file(self) -> None:
full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name)
if os.path.exists(full_service_file_path):
print(f'[!] Service file already exist [!]')
self.run_subprocess(self.config.service_cmd_executable)
return None
contain = f'''[Unit]
Description=Defender IRC Service
[Service]
ExecStart={self.config.venv_python_executable} {self.config.defender_main_executable}
WorkingDirectory={self.config.defender_install_folder}
SyslogIdentifier=Defender
Restart=on-failure
[Install]
WantedBy=default.target
'''
# Check if user systemd is available (.config/systemd/user/)
if not os.path.exists(self.config.unix_systemd_folder):
self.run_subprocess(['mkdir', '-p', self.config.unix_systemd_folder])
with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print('Service file generated with current configuration')
print('Running IRC Service ...')
self.run_subprocess(self.config.service_cmd_daemon_reload)
self.run_subprocess(self.config.service_cmd_executable)
else:
with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print('Service file generated with current configuration')
print('Running IRC Service ...')
self.run_subprocess(self.config.service_cmd_daemon_reload)
self.run_subprocess(self.config.service_cmd_executable)
def print_final_message(self) -> None:
print(f"#"*24)
print("Installation complete ...")
print("If the configuration is correct, then you must see your service connected to your irc server")
print(f"If any issue, you can see the log file for debug {self.config.defender_install_folder}{os.sep}logs{os.sep}defender.log")
print(f"#"*24)
exit(1)

View File

@@ -7,12 +7,12 @@ from ssl import SSLSocket
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, Optional, Union from typing import TYPE_CHECKING, Any, Optional, Union
from core.classes import rehash from core.classes import rehash
from core.loader import Loader from core.classes.protocols.interface import IProtocol
from core.classes.protocol import Protocol
from core.utils import tr from core.utils import tr
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MSasl from core.definition import MSasl
from core.loader import Loader
class Irc: class Irc:
_instance = None _instance = None
@@ -24,7 +24,7 @@ class Irc:
return cls._instance return cls._instance
def __init__(self, loader: Loader) -> 'Irc': def __init__(self, loader: 'Loader'):
# Loader class # Loader class
self.Loader = loader self.Loader = loader
@@ -135,7 +135,7 @@ class Irc:
############################################## ##############################################
# CONNEXION IRC # # CONNEXION IRC #
############################################## ##############################################
def init_irc(self, ircInstance: 'Irc') -> None: def init_irc(self) -> None:
"""Create a socket and connect to irc server """Create a socket and connect to irc server
Args: Args:
@@ -143,8 +143,8 @@ class Irc:
""" """
try: try:
self.init_service_user() self.init_service_user()
self.Utils.create_socket(ircInstance) self.Utils.create_socket(self)
self.__connect_to_irc(ircInstance) self.__connect_to_irc()
except AssertionError as ae: except AssertionError as ae:
self.Logs.critical(f'Assertion error: {ae}') self.Logs.critical(f'Assertion error: {ae}')
@@ -161,23 +161,20 @@ class Irc:
)) ))
return None return None
def __connect_to_irc(self, ircInstance: 'Irc') -> None: def __connect_to_irc(self) -> None:
try: try:
self.init_service_user() self.init_service_user()
self.ircObject = ircInstance # créer une copie de l'instance Irc self.Protocol: 'IProtocol' = self.Loader.PFactory.get()
self.Protocol = Protocol( self.Protocol.register_command()
protocol=self.Config.SERVEUR_PROTOCOL,
ircInstance=self.ircObject
).Protocol
self.Protocol.send_link() # Etablir le link en fonction du protocol choisi self.Protocol.send_link() # Etablir le link en fonction du protocol choisi
self.signal = True # Une variable pour initier la boucle infinie self.signal = True # Une variable pour initier la boucle infinie
self.join_saved_channels() # Join existing channels # self.join_saved_channels() # Join existing channels
self.ModuleUtils.db_load_all_existing_modules(self) # self.ModuleUtils.db_load_all_existing_modules(self)
while self.signal: while self.signal:
try: try:
if self.Config.DEFENDER_RESTART == 1: if self.Config.DEFENDER_RESTART == 1:
rehash.restart_service(self.ircObject) rehash.restart_service(self)
# 4072 max what the socket can grab # 4072 max what the socket can grab
buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) 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}") self.Logs.error(f"SSLEOFError __connect_to_irc: {soe} - {data}")
except ssl.SSLError as se: except ssl.SSLError as se:
self.Logs.error(f"SSLError __connect_to_irc: {se} - {data}") self.Logs.error(f"SSLError __connect_to_irc: {se} - {data}")
sys.exit(1) sys.exit(-1)
except OSError as oe: 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): except (socket.error, ConnectionResetError):
self.Logs.debug("Connexion reset") self.Logs.debug("Connexion reset")
@@ -220,7 +219,7 @@ class Irc:
except ssl.SSLEOFError as soe: except ssl.SSLEOFError as soe:
self.Logs.error(f"SSLEOFError: {soe}") self.Logs.error(f"SSLEOFError: {soe}")
except AttributeError as atte: except AttributeError as atte:
self.Logs.critical(f"AttributeError: {atte}") self.Logs.critical(f"AttributeError: {atte}", exc_info=True)
except Exception as e: except Exception as e:
self.Logs.critical(f"General Error: {e}", exc_info=True) self.Logs.critical(f"General Error: {e}", exc_info=True)
@@ -261,9 +260,9 @@ class Irc:
# This is only to reference the method # This is only to reference the method
return None return None
############################################## # --------------------------------------------
# FIN CONNEXION IRC # # FIN CONNEXION IRC #
############################################## # --------------------------------------------
def build_command(self, level: int, module_name: str, command_name: str, command_description: str) -> None: def build_command(self, level: int, module_name: str, command_name: str, command_description: str) -> None:
"""This method build the commands variable """This method build the commands variable
@@ -313,15 +312,15 @@ class Irc:
def db_get_admin_info(*, username: Optional[str] = None, password: Optional[str] = None, fingerprint: Optional[str] = None) -> Optional[dict[str, Any]]: def db_get_admin_info(*, username: Optional[str] = None, password: Optional[str] = None, fingerprint: Optional[str] = None) -> Optional[dict[str, Any]]:
if fingerprint: if fingerprint:
mes_donnees = {'fingerprint': fingerprint} mes_donnees = {'fingerprint': fingerprint}
query = f"SELECT user, level FROM {self.Config.TABLE_ADMIN} WHERE fingerprint = :fingerprint" query = f"SELECT user, level, language FROM {self.Config.TABLE_ADMIN} WHERE fingerprint = :fingerprint"
else: else:
mes_donnees = {'user': username, 'password': self.Utils.hash_password(password)} mes_donnees = {'user': username, 'password': self.Utils.hash_password(password)}
query = f"SELECT user, level FROM {self.Config.TABLE_ADMIN} WHERE user = :user AND 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) result = self.Base.db_execute_query(query, mes_donnees)
user_from_db = result.fetchone() user_from_db = result.fetchone()
if user_from_db: if user_from_db:
return {'user': user_from_db[0], 'level': user_from_db[1]} return {'user': user_from_db[0], 'level': user_from_db[1], 'language': user_from_db[2]}
else: else:
return None return None
@@ -331,6 +330,7 @@ class Irc:
if admin_info is not None: if admin_info is not None:
s.auth_success = True s.auth_success = True
s.level = admin_info.get('level', 0) s.level = admin_info.get('level', 0)
s.language = admin_info.get('language', 'EN')
self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} SASL {self.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S") self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} SASL {self.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S")
self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful") self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful")
else: else:
@@ -345,6 +345,7 @@ class Irc:
s.auth_success = True s.auth_success = True
s.level = admin_info.get('level', 0) s.level = admin_info.get('level', 0)
s.username = admin_info.get('user', None) s.username = admin_info.get('user', None)
s.language = admin_info.get('language', 'EN')
self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} SASL {self.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S") self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} SASL {self.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D S")
self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful") self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} 903 {s.username} :SASL authentication successful")
else: else:
@@ -380,14 +381,16 @@ class Irc:
time.sleep(beat) time.sleep(beat)
self.Base.execute_periodic_action() self.Base.execute_periodic_action()
def insert_db_admin(self, uid: str, account: str, level: int) -> None: def insert_db_admin(self, uid: str, account: str, level: int, language: str) -> None:
user_obj = self.User.get_user(uid) user_obj = self.User.get_user(uid)
if user_obj is None: if user_obj is None:
return None return None
self.Admin.insert( self.Admin.insert(
self.Loader.Definition.MAdmin( self.Loader.Definition.MAdmin(
**user_obj.to_dict(), **user_obj.to_dict(),
language=language,
account=account, account=account,
level=int(level) level=int(level)
) )
@@ -405,57 +408,58 @@ class Irc:
return None 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] # > addaccess [nickname] [level] [password]
dnick = self.Config.SERVICE_NICKNAME
p = self.Protocol
get_user = self.User.get_user(nickname) get_user = self.User.get_user(new_admin)
level = self.Base.convert_to_int(level) level = self.Base.convert_to_int(new_level)
password = password password = new_password
if get_user is None: if get_user is None:
response = f'This nickname {nickname} does not exist, it is not possible to create this user' response = tr("The nickname (%s) is not currently connected! please create a new admin when the nickname is connected to the network!", new_admin)
self.Logs.warning(response) p.send_notice(dnick, sender, response)
return response self.Logs.debug(f"New admin {new_admin} sent by {sender} is not connected")
return False
if level is None: if level is None or level > 4 or level == 0:
response = f'The level [{level}] must be a number from 1 to 4' p.send_notice(dnick, sender, tr("The level (%s) must be a number from 1 to 4", level))
self.Logs.warning(response) self.Logs.debug(f"Level must a number between 1 to 4 (sent by {sender})")
return response return False
if level > 4:
response = "Impossible d'ajouter un niveau > 4"
self.Logs.warning(response)
return response
nickname = get_user.nickname nickname = get_user.nickname
response = ''
hostname = get_user.hostname hostname = get_user.hostname
vhost = get_user.vhost vhost = get_user.vhost
spassword = self.Loader.Utils.hash_password(password) spassword = self.Loader.Utils.hash_password(password)
mes_donnees = {'admin': nickname} # Check if the user already exist
query_search_user = f"SELECT id FROM {self.Config.TABLE_ADMIN} WHERE user=:admin" if not self.Admin.db_is_admin_exist(nickname):
r = self.Base.db_execute_query(query_search_user, mes_donnees) mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level, 'language': 'EN'}
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}
self.Base.db_execute_query(f'''INSERT INTO {self.Config.TABLE_ADMIN} self.Base.db_execute_query(f'''INSERT INTO {self.Config.TABLE_ADMIN}
(createdOn, user, password, hostname, vhost, level) VALUES (createdOn, user, password, hostname, vhost, level, language) VALUES
(:datetime, :user, :password, :hostname, :vhost, :level) (:datetime, :user, :password, :hostname, :vhost, :level, :language)
''', mes_donnees) ''', 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) p.send_notice(dnick, sender, tr("New admin (%s) has been added with level %s", nickname, level))
self.Logs.info(response) self.Logs.info(f"A new admin ({nickname}) has been created by {sender}!")
return response return True
else: else:
response = f'{nickname} Existe déjà dans les users enregistrés' p.send_notice(dnick, sender, tr("The nickname (%s) Already exist!", nickname))
self.Protocol.send_notice(nick_from=self.Config.SERVICE_NICKNAME, nick_to=nickname, msg=response) self.Logs.info(f"The nickname {nickname} already exist! (sent by {sender})")
self.Logs.info(response) return False
return response
def thread_check_for_new_version(self, fromuser: str) -> None: def thread_check_for_new_version(self, fromuser: str) -> None:
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
@@ -485,117 +489,12 @@ class Irc:
return None return None
self.Logs.debug(f">> {self.Utils.hide_sensitive_data(original_response)}") self.Logs.debug(f">> {self.Utils.hide_sensitive_data(original_response)}")
parsed_protocol = self.Protocol.parse_server_msg(original_response.copy())
match parsed_protocol:
case 'PING': pos, parsed_protocol = self.Protocol.get_ircd_protocol_poisition(cmd=original_response, log=True)
self.Protocol.on_server_ping(serverMsg=original_response)
case 'SERVER': for parsed in self.Protocol.Handler.get_ircd_commands():
self.Protocol.on_server(serverMsg=original_response) if parsed.command_name.upper() == parsed_protocol:
parsed.func(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)
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':
# >> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r']
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':
# [':adator_', 'UMODE2', '-i']
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 'SLOG': # TODO
self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}")
case 'MD': # TODO
self.Logs.debug(f"[!] TO HANDLE: {parsed_protocol}")
case 'PRIVMSG':
self.Protocol.on_privmsg(serverMsg=original_response)
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}")
if len(original_response) > 2: if len(original_response) > 2:
if original_response[2] != 'UID': if original_response[2] != 'UID':
@@ -622,7 +521,8 @@ class Irc:
""" """
fromuser = self.User.get_nickname(user) # Nickname qui a lancé la commande fromuser = self.User.get_nickname(user) # Nickname qui a lancé la commande
uid = self.User.get_uid(fromuser) # Récuperer le uid de l'utilisateur uid = self.User.get_uid(user) # Récuperer le uid de l'utilisateur
self.Settings.current_admin = self.Admin.get_admin(user) # set Current admin if any.
RED = self.Config.COLORS.red RED = self.Config.COLORS.red
GREEN = self.Config.COLORS.green GREEN = self.Config.COLORS.green
@@ -677,6 +577,9 @@ class Irc:
channel=dchanlog channel=dchanlog
) )
self.Protocol.send_notice(dnickname, fromuser, tr("You have been successfully disconnected from %s", dnickname))
return None
case 'firstauth': case 'firstauth':
# firstauth OWNER_NICKNAME OWNER_PASSWORD # firstauth OWNER_NICKNAME OWNER_PASSWORD
current_nickname = self.User.get_nickname(fromuser) current_nickname = self.User.get_nickname(fromuser)
@@ -736,7 +639,7 @@ class Irc:
if cmd_owner == config_owner and cmd_password == config_password: if cmd_owner == config_owner and cmd_password == config_password:
self.Base.db_create_first_admin() self.Base.db_create_first_admin()
self.insert_db_admin(current_uid, cmd_owner, 5) self.insert_db_admin(current_uid, cmd_owner, 5, self.Config.LANG)
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(
msg=f"[ {self.Config.COLORS.green}{str(current_command).upper()} ]{self.Config.COLORS.black} - {self.User.get_nickname(fromuser)} est désormais connecté a {dnickname}", msg=f"[ {self.Config.COLORS.green}{str(current_command).upper()} ]{self.Config.COLORS.black} - {self.User.get_nickname(fromuser)} est désormais connecté a {dnickname}",
nick_from=dnickname, nick_from=dnickname,
@@ -782,19 +685,21 @@ class Irc:
if admin_obj: if admin_obj:
self.Protocol.send_priv_msg(nick_from=dnickname, 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) channel=dchanlog)
self.Protocol.send_notice(dnickname, fromuser, tr("You are already connected to %s", dnickname))
return None return None
mes_donnees = {'user': user_to_log, 'password': self.Loader.Utils.hash_password(password)} mes_donnees = {'user': user_to_log, 'password': self.Loader.Utils.hash_password(password)}
query = f"SELECT id, user, level FROM {self.Config.TABLE_ADMIN} WHERE user = :user AND password = :password" query = f"SELECT id, user, level, language FROM {self.Config.TABLE_ADMIN} WHERE user = :user AND password = :password"
result = self.Base.db_execute_query(query, mes_donnees) result = self.Base.db_execute_query(query, mes_donnees)
user_from_db = result.fetchone() user_from_db = result.fetchone()
if user_from_db: if user_from_db:
account = user_from_db[1] account = str(user_from_db[1])
level = user_from_db[2] level = int(user_from_db[2])
self.insert_db_admin(current_client.uid, account, level) language = str(user_from_db[3])
self.insert_db_admin(current_client.uid, account, level, language)
self.Protocol.send_priv_msg(nick_from=dnickname, self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {GREEN}{str(current_command).upper()}{NOGC} ] - {current_client.nickname} ({account}) est désormais connecté a {dnickname}", msg=f"[ {GREEN}{str(current_command).upper()}{NOGC} ] - {current_client.nickname} ({account}) est désormais connecté a {dnickname}",
channel=dchanlog) channel=dchanlog)
@@ -813,15 +718,14 @@ class Irc:
if len(cmd) < 4: 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"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") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"level: from 1 to 4")
return None
newnickname = cmd[1] new_admin = str(cmd[1])
newlevel = self.Base.int_if_possible(cmd[2]) level = self.Base.int_if_possible(cmd[2])
password = cmd[3] password = str(cmd[3])
response = self.create_defender_user(newnickname, newlevel, password) self.create_defender_user(fromuser, new_admin, level, password)
return None
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{response}")
self.Logs.info(response)
except IndexError as ie: except IndexError as ie:
self.Logs.error(f'_hcmd addaccess: {ie}') self.Logs.error(f'_hcmd addaccess: {ie}')
@@ -943,12 +847,15 @@ class Irc:
try: try:
admin_obj = self.Admin.get_admin(fromuser) admin_obj = self.Admin.get_admin(fromuser)
if admin_obj: if admin_obj:
query = f'UPDATE {self.Config.TABLE_ADMIN} SET fingerprint = :fingerprint WHERE user = :user' if admin_obj.fingerprint is not None:
r = self.Base.db_execute_query(query, {'fingerprint': admin_obj.fingerprint, 'user': admin_obj.account}) query = f'UPDATE {self.Config.TABLE_ADMIN} SET fingerprint = :fingerprint WHERE user = :user'
if r.rowcount > 0: r = self.Base.db_execute_query(query, {'fingerprint': admin_obj.fingerprint, 'user': admin_obj.account})
self.Protocol.send_notice(dnickname, fromuser, f'[ {GREEN}CERT{NOGC} ] Your new fingerprint has been attached to your account. {admin_obj.fingerprint}') if r.rowcount > 0:
self.Protocol.send_notice(dnickname, fromuser, f'[ {GREEN}CERT{NOGC} ] Your new fingerprint has been attached to your account. {admin_obj.fingerprint}')
else:
self.Protocol.send_notice(dnickname, fromuser, f'[ {RED}CERT{NOGC} ] Impossible to add your fingerprint.{admin_obj.fingerprint}')
else: else:
self.Protocol.send_notice(dnickname, fromuser, f'[ {RED}CERT{NOGC} ] Impossible to add your fingerprint.{admin_obj.fingerprint}') self.Protocol.send_notice(dnickname, fromuser, f'[ {RED}CERT{NOGC} ] There is no fingerprint to add.')
except Exception as e: except Exception as e:
self.Logs.error(e) self.Logs.error(e)
@@ -1110,7 +1017,7 @@ class Irc:
except KeyError as ke: except KeyError as ke:
self.Logs.error(f"Key Error: {ke} - list recieved: {cmd}") self.Logs.error(f"Key Error: {ke} - list recieved: {cmd}")
except Exception as err: 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': case 'unload':
# unload mod_defender # unload mod_defender
@@ -1146,7 +1053,8 @@ class Irc:
self.Base.execute_periodic_action() self.Base.execute_periodic_action()
for chan_name in self.Channel.UID_CHANNEL_DB: 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: for client in self.Client.CLIENT_DB:
self.Protocol.send_svslogout(client) self.Protocol.send_svslogout(client)
@@ -1171,7 +1079,7 @@ class Irc:
self.Config.DEFENDER_INIT = 1 # set init to 1 saying that the service will be re initiated self.Config.DEFENDER_INIT = 1 # set init to 1 saying that the service will be re initiated
case 'rehash': case 'rehash':
rehash.rehash_service(self.ircObject, fromuser) rehash.rehash_service(self, fromuser)
return None return None
case 'show_modules': case 'show_modules':
@@ -1192,14 +1100,14 @@ class Irc:
self.Protocol.send_notice( self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
msg=f"{module} - {GREEN}Loaded{NOGC} by {loaded_user} on {loaded_datetime}" msg=tr('%s - %sLoaded%s by %s on %s', module, GREEN, NOGC, loaded_user, loaded_datetime)
) )
loaded = False loaded = False
else: else:
self.Protocol.send_notice( self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
msg=f"{module} - {RED}Not Loaded{NOGC}" msg=tr('%s - %sNot Loaded%s', module, RED, NOGC)
) )
case 'show_timers': case 'show_timers':
@@ -1269,7 +1177,7 @@ class Irc:
self.Protocol.send_notice( self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
msg=f"UID : {db_admin.uid} - Nickname: {db_admin.nickname} - Account: {db_admin.account} - Level: {db_admin.level} - Connection: {db_admin.connexion_datetime}" msg=f"UID : {db_admin.uid} - Nickname: {db_admin.nickname} - Account: {db_admin.account} - Level: {db_admin.level} - Language: {db_admin.language} - Connection: {db_admin.connexion_datetime}"
) )
return None return None
@@ -1312,7 +1220,7 @@ class Irc:
self.Protocol.send_notice( self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
msg=f">> Defender V.{self.Config.CURRENT_VERSION} Developped by adator®." msg=f">> Defender V{self.Config.CURRENT_VERSION} Developped by adator®."
) )
return None return None

View File

@@ -2,3 +2,12 @@ traduction:
# Message help # Message help
- orig: "Access denied!" - orig: "Access denied!"
trad: "Accès refusé." trad: "Accès refusé."
- orig: "%s - %sLoaded%s by %s on %s"
trad: "%s - %sChargé%s par %s le %s"
- orig: "%s - %sNot Loaded%s"
trad: "%s - %sNon chargé%s"
- orig: "Successfuly connected to %s"
trad: "Connecter a %s avec succés"
- orig: "[ %sINFORMATION%s ] >> %s is ready!"
trad: "[ %sINFORMATION%s ] >> %s est prêt!"

View File

@@ -8,6 +8,8 @@ import core.base as base_mod
import core.module as module_mod import core.module as module_mod
import core.classes.commands as commands_mod import core.classes.commands as commands_mod
import core.classes.config as conf_mod import core.classes.config as conf_mod
import core.irc as irc
import core.classes.protocols.factory as factory
class Loader: class Loader:
@@ -33,7 +35,7 @@ class Loader:
self.Logs: Logger = self.ServiceLogging.get_logger() self.Logs: Logger = self.ServiceLogging.get_logger()
self.Config: df.MConfig = self.ConfModule.Configuration(self).get_config_model() self.Config: df.MConfig = self.ConfModule.Configuration(self).configuration_model
self.Settings.global_lang = self.Config.LANG if self.Config.LANG else "EN" self.Settings.global_lang = self.Config.LANG if self.Config.LANG else "EN"
@@ -63,4 +65,8 @@ class Loader:
self.Sasl: sasl.Sasl = sasl.Sasl(self) 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__)) self.Logs.debug(self.Utils.tr("Loader %s success", __name__))

View File

@@ -15,7 +15,7 @@ class ServiceLogging:
self.SERVER_PREFIX = None self.SERVER_PREFIX = None
self.LOGGING_CONSOLE = True 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.file_handler = None
self.stdout_handler = None self.stdout_handler = None

View File

@@ -206,6 +206,7 @@ class Module:
module = self.model_get_module(module_name) module = self.model_get_module(module_name)
if module is None: if module is None:
self.__Logs.debug(f"[ UNLOAD MODULE ERROR ] This module {module_name} is not loaded!") 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( uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME, nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}UNLOAD MODULE ERROR{nogc} ] This module {module_name} is not loaded!", msg=f"[ {red}UNLOAD MODULE ERROR{nogc} ] This module {module_name} is not loaded!",

View File

@@ -2,7 +2,6 @@
Main utils library. Main utils library.
''' '''
import gc import gc
import glob
import ssl import ssl
import socket import socket
import sys import sys
@@ -37,28 +36,27 @@ def tr(message: str, *args) -> str:
is_args_available = True if args else False is_args_available = True if args else False
g = global_settings g = global_settings
try: try:
# Access to user object ==> global_instance.get_user_option # Access to admin object
client_language = global_settings.global_user.current_user.geoip if global_settings.global_user.current_user else 'en' client_language = g.current_admin.language if g.current_admin else g.global_lang
client_language = client_language if client_language else 'en'
if count_args != count_placeholder: if count_args != count_placeholder:
global_settings.global_logger.error(f"Translation: Original message: {message} | Args: {count_args} - Placeholder: {count_placeholder}") g.global_logger.error(f"Translation: Original message: {message} | Args: {count_args} - Placeholder: {count_placeholder}")
return message return message
if g.global_lang is None: if g.global_lang is None:
return message % args if is_args_available else message return message % args if is_args_available else message
if g.global_lang.lower() == 'en': if client_language.lower() == 'en':
return message % args if is_args_available else message return message % args if is_args_available else message
for trads in global_settings.global_translation[global_settings.global_lang.lower()]: for trads in g.global_translation[client_language.lower()]:
if sub(r"\s+", "", message) == sub(r"\s+", "", trads[0]): if sub(r"\s+", "", message) == sub(r"\s+", "", trads[0]):
return trads[1] % args if is_args_available else trads[1] return trads[1] % args if is_args_available else trads[1]
return message % args if is_args_available else message return message % args if is_args_available else message
except KeyError as ke: 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 return message % args if is_args_available else message
except Exception as err: except Exception as err:
@@ -145,6 +143,8 @@ def create_socket(uplink: 'Irc') -> None:
uplink.Logs.critical(f"[OS Error]: {oe}") uplink.Logs.critical(f"[OS Error]: {oe}")
if 'connection refused' in str(oe).lower(): if 'connection refused' in str(oe).lower():
sys.exit(oe) sys.exit(oe)
if oe.errno == 10053:
sys.exit(oe)
except AttributeError as ae: except AttributeError as ae:
uplink.Logs.critical(f"AttributeError: {ae}") uplink.Logs.critical(f"AttributeError: {ae}")

View File

@@ -1,24 +1,22 @@
from core import installation from core import install
############################################# #############################################
# @Version : 6.2 # # @Version : 6.3 #
# Requierements : # # Requierements : #
# Python3.10 or higher # # Python3.10 or higher #
# SQLAlchemy, requests, psutil # # SQLAlchemy, requests, psutil #
# unrealircd-rpc-py # # unrealircd-rpc-py, pyyaml #
# UnrealIRCD 6.2.2 or higher # # UnrealIRCD 6.2.2 or higher #
############################################# #############################################
try: try:
install.update_packages()
installation.Install()
from core.loader import Loader from core.loader import Loader
from core.irc import Irc loader = Loader()
ircInstance = Irc(Loader()) loader.Irc.init_irc()
ircInstance.init_irc(ircInstance)
except AssertionError as ae: except AssertionError as ae:
print(f'Assertion Error -> {ae}') print(f'Assertion Error -> {ae}')
except KeyboardInterrupt as k: except KeyboardInterrupt as k:
ircInstance.Base.execute_periodic_action() # ircInstance.Base.execute_periodic_action()
...

View File

@@ -78,11 +78,11 @@ class Clone:
self.__load_module_configuration() self.__load_module_configuration()
self.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL) 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: 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.
@@ -127,8 +127,8 @@ class Clone:
self.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB) 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.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.send_set_mode('-nts', channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -k {self.Config.CLONE_CHANNEL_PASSWORD}") 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.Protocol.send_part_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL)
self.Irc.Commands.drop_command_by_module(self.module_name) self.Irc.Commands.drop_command_by_module(self.module_name)
@@ -148,7 +148,8 @@ class Clone:
match command: match command:
case 'PRIVMSG': case 'PRIVMSG':
return self.Utils.handle_on_privmsg(self, cmd) self.Utils.handle_on_privmsg(self, cmd)
return None
case 'QUIT': case 'QUIT':
return None return None
@@ -202,7 +203,7 @@ class Clone:
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect [number of clone you want to connect] [Group]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect [number of clone you want to connect] [Group] [freq]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Exemple /msg {dnickname} clone connect 6 Ambiance") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Exemple /msg {dnickname} clone connect 6 Ambiance")
case 'kill': case 'kill':
@@ -232,8 +233,7 @@ class Clone:
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill all") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group name | nickname]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill clone_nickname")
case 'join': case 'join':
try: try:
@@ -262,8 +262,7 @@ class Clone:
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join all #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join clone_nickname #channel")
case 'part': case 'part':
try: try:
@@ -293,8 +292,7 @@ class Clone:
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part all #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part clone_nickname #channel")
case 'list': case 'list':
try: try:

View File

@@ -174,17 +174,17 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
return True 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) senderObj = uplink.User.get_user(uid_sender)
if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT: if senderObj is not None:
return if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT:
return
if not senderObj is None: senderMsg = parser.get('message', None)
senderMsg = ' '.join(srvmsg[4:]) clone_obj = uplink.Clone.get_clone(parser.get('uid_reciever', None))
clone_obj = uplink.Clone.get_clone(srvmsg[3])
if clone_obj is None: if clone_obj is None:
return return
@@ -196,3 +196,5 @@ def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]):
msg=final_message, msg=final_message,
channel=uplink.Config.CLONE_CHANNEL channel=uplink.Config.CLONE_CHANNEL
) )
return None

View File

@@ -53,16 +53,16 @@ class Command:
# Module Utils # Module Utils
self.mod_utils = utils self.mod_utils = utils
self.Irc.build_command(1, self.module_name, 'join', 'Join a channel') self.Irc.build_command(2, 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(2, 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(2, 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(2, 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(2, 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(2, 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(2, 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(2, 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(2, 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, '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, '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, 'dehalfop', 'Remove half-operator privileges from a user')
self.Irc.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user') self.Irc.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user')

View File

@@ -134,17 +134,20 @@ def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str]
return False return False
if len(cmd) == 1: 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 return None
# deop nickname # deop nickname
if len(cmd) == 2: if len(cmd) == 2:
nickname = cmd[1] 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 return None
nickname = cmd[2] 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 return None
def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None: def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None:

View File

@@ -217,7 +217,7 @@ class Defender:
if response is not None: if response is not None:
q_insert = "INSERT INTO def_trusted (datetime, user, host, vhost) VALUES (?, ?, ?, ?)" q_insert = "INSERT INTO def_trusted (datetime, user, host, vhost) VALUES (?, ?, ?, ?)"
mes_donnees = {'datetime': self.Base.get_datetime(), 'user': nickname, 'host': '*', 'vhost': '*'} mes_donnees = {'datetime': self.Loader.Utils.get_datetime(), 'user': nickname, 'host': '*', 'vhost': '*'}
exec_query = self.Base.db_execute_query(q_insert, mes_donnees) exec_query = self.Base.db_execute_query(q_insert, mes_donnees)
pass pass
@@ -365,7 +365,7 @@ class Defender:
release_code = cmd[1] release_code = cmd[1]
jailed_nickname = self.User.get_nickname(fromuser) jailed_nickname = self.User.get_nickname(fromuser)
jailed_UID = self.User.get_uid(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: if get_reputation is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" No code is requested ...") 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!") msg=f"This nickname ({str(cmd[2])}) is not connected to the network!")
return None 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: if client_to_release is None:
p.send_notice(nick_from=dnickname, p.send_notice(nick_from=dnickname,
@@ -949,7 +949,7 @@ class Defender:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' WebWebsocket : {UserObject.isWebsocket}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' WebWebsocket : {UserObject.isWebsocket}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' REPUTATION : {UserObject.score_connexion}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' REPUTATION : {UserObject.score_connexion}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' MODES : {UserObject.umodes}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' MODES : {UserObject.umodes}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CHANNELS : {channels}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CHANNELS : {", ".join(channels)}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CONNECTION TIME : {UserObject.connexion_datetime}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CONNECTION TIME : {UserObject.connexion_datetime}')
else: else:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {nickoruid} doesn't exist") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {nickoruid} doesn't exist")

View File

@@ -87,7 +87,7 @@ def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]):
return return
if confmodel.reputation == 1: 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: if parsed_chan != gconfig.SALON_JAIL:
p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b ~security-group:unknown-users") 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 return None
def handle_on_nick(uplink: 'Defender', srvmsg: list[str]): def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
"""_summary_ """Handle nickname changes.
>>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'newnickname', '1754663712'] >>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'newnickname', '1754663712']
>>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740']
Args: Args:
irc_instance (Irc): The Irc instance irc_instance (Irc): The Irc instance
srvmsg (list[str]): The Server MSG srvmsg (list[str]): The Server MSG
confmodel (ModConfModel): The Module Configuration confmodel (ModConfModel): The Module Configuration
""" """
uid = uplink.Loader.Utils.clean_uid(str(srvmsg[1]))
p = uplink.Protocol p = uplink.Protocol
parser = p.parse_nick(srvmsg)
uid = uplink.Loader.Utils.clean_uid(parser.get('uid', None))
confmodel = uplink.ModConfig confmodel = uplink.ModConfig
get_reputation = uplink.Reputation.get_Reputation(uid) get_reputation = uplink.Reputation.get_reputation(uid)
jail_salon = uplink.Config.SALON_JAIL jail_salon = uplink.Config.SALON_JAIL
service_id = uplink.Config.SERVICE_ID service_id = uplink.Config.SERVICE_ID
@@ -159,7 +161,7 @@ def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
# Update the new nickname # Update the new nickname
oldnick = get_reputation.nickname oldnick = get_reputation.nickname
newnickname = srvmsg[3] newnickname = parser.get('newnickname', None)
get_reputation.nickname = newnickname get_reputation.nickname = newnickname
# If ban in all channel is ON then unban old nickname an ban the new nickname # 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}!*@*") p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*")
def handle_on_quit(uplink: 'Defender', srvmsg: list[str]): def handle_on_quit(uplink: 'Defender', srvmsg: list[str]):
"""_summary_ """Handle on quit message
>>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', 'quit message'] >>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', 'quit message']
Args: Args:
uplink (Irc): The Defender Module instance uplink (Irc): The Defender Module instance
srvmsg (list[str]): The Server MSG srvmsg (list[str]): The Server MSG
""" """
p = uplink.Protocol p = uplink.Protocol
parser = p.parse_quit(srvmsg)
confmodel = uplink.ModConfig confmodel = uplink.ModConfig
ban_all_chan = uplink.Base.int_if_possible(confmodel.reputation_ban_all_chan) 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 jail_salon = uplink.Config.SALON_JAIL
service_id = uplink.Config.SERVICE_ID 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: if get_user_reputation is not None:
final_nickname = get_user_reputation.nickname final_nickname = get_user_reputation.nickname
@@ -204,6 +207,7 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
uplink (Defender): The Defender instance uplink (Defender): The Defender instance
srvmsg (list[str]): The Server MSG srvmsg (list[str]): The Server MSG
""" """
parser_uid = uplink.Protocol.parse_uid(srvmsg)
gconfig = uplink.Config gconfig = uplink.Config
irc = uplink.Irc irc = uplink.Irc
confmodel = uplink.ModConfig confmodel = uplink.ModConfig
@@ -213,10 +217,10 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
return None return None
# Get User information # 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: 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 return
# If user is not service or IrcOp then scan them # 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 # # ACTION FUNCTIONS #
#################### ####################
# [:<sid>] UID <uid> <ts> <nick> <real-host> <displayed-host> <real-user> <ip> <signon> <modes> [<mode-parameters>]+ :<real>
# [:<sid>] UID nickname hopcount timestamp username hostname uid servicestamp umodes virthost cloakedhost ip :gecos
def action_on_flood(uplink: 'Defender', srvmsg: list[str]): def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
confmodel = uplink.ModConfig confmodel = uplink.ModConfig
@@ -318,7 +323,7 @@ def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
p = uplink.Protocol p = uplink.Protocol
confmodel = uplink.ModConfig confmodel = uplink.ModConfig
get_reputation = irc.Reputation.get_Reputation(jailed_uid) get_reputation = irc.Reputation.get_reputation(jailed_uid)
if get_reputation is None: if get_reputation is None:
irc.Logs.warning(f'UID {jailed_uid} has not been found') irc.Logs.warning(f'UID {jailed_uid} has not been found')
@@ -378,14 +383,11 @@ def action_apply_reputation_santions(uplink: 'Defender') -> None:
color_red = gconfig.COLORS.red color_red = gconfig.COLORS.red
nogc = gconfig.COLORS.nogc nogc = gconfig.COLORS.nogc
salon_jail = gconfig.SALON_JAIL salon_jail = gconfig.SALON_JAIL
if reputation_flag == 0:
return None
elif reputation_timer == 0:
return None
uid_to_clean = [] uid_to_clean = []
if reputation_flag == 0 or reputation_timer == 0:
return None
for user in irc.Reputation.UID_REPUTATION_DB: for user in irc.Reputation.UID_REPUTATION_DB:
if not user.isWebirc: # Si il ne vient pas de WebIRC if not user.isWebirc: # Si il ne vient pas de WebIRC
if irc.User.get_user_uptime_in_minutes(user.uid) >= reputation_timer and int(user.score_connexion) <= int(reputation_seuil): if irc.User.get_user_uptime_in_minutes(user.uid) >= reputation_timer and int(user.score_connexion) <= int(reputation_seuil):
@@ -404,7 +406,7 @@ def action_apply_reputation_santions(uplink: 'Defender') -> None:
# Suppression des éléments dans {UID_DB} et {REPUTATION_DB} # Suppression des éléments dans {UID_DB} et {REPUTATION_DB}
for chan in irc.Channel.UID_CHANNEL_DB: for chan in irc.Channel.UID_CHANNEL_DB:
if chan.name != salon_jail and ban_all_chan == 1: 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}!*@*") p.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*")
# Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}. # Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}.

View File

@@ -1,13 +1,13 @@
import logging import logging
import asyncio import asyncio
from unrealircd_rpc_py.objects.Definition import LiveRPCResult
import mods.jsonrpc.utils as utils import mods.jsonrpc.utils as utils
import mods.jsonrpc.threads as thds import mods.jsonrpc.threads as thds
from time import sleep from time import sleep
from types import SimpleNamespace
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dataclasses import dataclass from dataclasses import dataclass
from unrealircd_rpc_py.Live import LiveWebsocket, LiveUnixSocket from unrealircd_rpc_py.ConnectionFactory import ConnectionFactory
from unrealircd_rpc_py.Loader import Loader from unrealircd_rpc_py.LiveConnectionFactory import LiveConnectionFactory
if TYPE_CHECKING: if TYPE_CHECKING:
from core.irc import Irc from core.irc import Irc
@@ -85,43 +85,35 @@ class Jsonrpc():
self.__load_module_configuration() self.__load_module_configuration()
# End of mandatory methods you can start your customization # # End of mandatory methods you can start your customization #
self.UnrealIrcdRpcLive: LiveWebsocket = LiveWebsocket( try:
url=self.Config.JSONRPC_URL, self.Rpc = ConnectionFactory(self.Config.DEBUG_LEVEL).get(self.Config.JSONRPC_METHOD)
username=self.Config.JSONRPC_USER, self.LiveRpc = LiveConnectionFactory(self.Config.DEBUG_LEVEL).get(self.Config.JSONRPC_METHOD)
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: sync_unixsocket = {'path_to_socket_file': self.Config.JSONRPC_PATH_TO_SOCKET_FILE}
self.Logs.error(f"{self.UnrealIrcdRpcLive.get_error.message} ({self.UnrealIrcdRpcLive.get_error.code})") sync_http = {'url': self.Config.JSONRPC_URL, 'username': self.Config.JSONRPC_USER, 'password': self.Config.JSONRPC_PASSWORD}
live_unixsocket = {'path_to_socket_file': self.Config.JSONRPC_PATH_TO_SOCKET_FILE,
'callback_object_instance' : self, 'callback_method_or_function_name': 'callback_sent_to_irc'}
live_http = {'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'}
sync_param = sync_unixsocket if self.Config.JSONRPC_METHOD == 'unixsocket' else sync_http
live_param = live_unixsocket if self.Config.JSONRPC_METHOD == 'unixsocket' else live_http
self.Rpc.setup(sync_param)
self.LiveRpc.setup(live_param)
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( self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME, 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 channel=self.Config.SERVICE_CHANLOG
) )
raise Exception(f"[LIVE-JSONRPC ERROR] {self.UnrealIrcdRpcLive.get_error.message}") self.Logs.error(f"JSONRPC ERROR: {err.__str__()}")
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
def __create_tables(self) -> None: 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.
@@ -143,7 +135,7 @@ class Jsonrpc():
self.Base.db_execute_query(table_logs) self.Base.db_execute_query(table_logs)
return None 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 dnickname = self.Config.SERVICE_NICKNAME
dchanlog = self.Config.SERVICE_CHANLOG dchanlog = self.Config.SERVICE_CHANLOG
@@ -152,29 +144,19 @@ class Jsonrpc():
bold = self.Config.COLORS.bold bold = self.Config.COLORS.bold
red = self.Config.COLORS.red 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, 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) channel=dchanlog)
return None return None
if hasattr(response, 'error'): if isinstance(response.result, bool):
if response.error.code != 0: if response.result:
self.Protocol.send_priv_msg( 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, nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
channel=dchanlog) channel=dchanlog)
return None return None
level = response.result.level if hasattr(response.result, 'level') else '' level = response.result.level if hasattr(response.result, 'level') else ''
subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else '' subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else ''
@@ -278,18 +260,13 @@ class Jsonrpc():
match option: match option:
case 'get': case 'get':
nickname = str(cmd[2]) nickname = str(cmd[2])
uid_to_get = self.User.get_uid(nickname)
if uid_to_get is None:
return None
rpc = self.Rpc rpc = self.Rpc
UserInfo = rpc.User.get(uid_to_get) UserInfo = rpc.User.get(nickname)
if rpc.get_error.code != 0: if UserInfo.error.code != 0:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.get_error.message}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{UserInfo.error.message}')
return None 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'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'NICKNAME : {UserInfo.name}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}')
@@ -321,9 +298,8 @@ class Jsonrpc():
case 'jrinstances': case 'jrinstances':
try: 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"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 LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveConnectionFactory)}")
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 ConnectionFactory: {self.MainUtils.get_number_gc_objects(ConnectionFactory)}")
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 de toute les instances: {self.MainUtils.get_number_gc_objects()}") 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: except Exception as err:
self.Logs.error(f"Unknown Error: {err}") self.Logs.error(f"Unknown Error: {err}")

View File

@@ -5,24 +5,20 @@ if TYPE_CHECKING:
from mods.jsonrpc.mod_jsonrpc import Jsonrpc from mods.jsonrpc.mod_jsonrpc import Jsonrpc
def thread_subscribe(uplink: 'Jsonrpc') -> None: def thread_subscribe(uplink: 'Jsonrpc') -> None:
response: dict[str, dict] = {}
snickname = uplink.Config.SERVICE_NICKNAME snickname = uplink.Config.SERVICE_NICKNAME
schannel = uplink.Config.SERVICE_CHANLOG schannel = uplink.Config.SERVICE_CHANLOG
uplink.is_streaming = True
response = asyncio.run(uplink.LiveRpc.subscribe(["all"]))
if uplink.UnrealIrcdRpcLive.get_error.code == 0: if response.error.code != 0:
uplink.is_streaming = True
response = asyncio.run(uplink.UnrealIrcdRpcLive.subscribe(["all"]))
else:
uplink.Protocol.send_priv_msg(nick_from=snickname, 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 channel=schannel
) )
if response is None: code = response.error.code
return message = response.error.message
code = response.get('error', {}).get('code', 0)
message = response.get('error', {}).get('message', None)
if code == 0: if code == 0:
uplink.Protocol.send_priv_msg( uplink.Protocol.send_priv_msg(
@@ -39,18 +35,15 @@ def thread_subscribe(uplink: 'Jsonrpc') -> None:
def thread_unsubscribe(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.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!")
uplink.is_streaming = False uplink.is_streaming = False
uplink.update_configuration('jsonrpc', 0) uplink.update_configuration('jsonrpc', 0)
snickname = uplink.Config.SERVICE_NICKNAME snickname = uplink.Config.SERVICE_NICKNAME
schannel = uplink.Config.SERVICE_CHANLOG schannel = uplink.Config.SERVICE_CHANLOG
if response is None: code = response.error.code
return None message = response.error.message
code = response.get('error', {}).get('code', 0)
message = response.get('error', {}).get('message', None)
if code != 0: if code != 0:
uplink.Protocol.send_priv_msg( uplink.Protocol.send_priv_msg(

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
Faker==37.12.0
psutil==7.1.2
PyYAML==6.0.3
requests==2.32.5
SQLAlchemy==2.0.44
unrealircd_rpc_py==3.0.2

View File

@@ -1,10 +1,10 @@
{ {
"version": "6.2.5", "version": "6.3.2",
"requests": "2.32.3", "requests": "2.32.5",
"psutil": "6.0.0", "psutil": "7.1.2",
"unrealircd_rpc_py": "2.0.5", "unrealircd_rpc_py": "3.0.1",
"sqlalchemy": "2.0.35", "sqlalchemy": "2.0.44",
"faker": "30.1.0", "faker": "37.12.0",
"pyyaml": "6.0.2" "pyyaml": "6.0.3"
} }