72 Commits

Author SHA1 Message Date
adator
c371910066 Creating JSONRPC Server 2025-11-16 19:13:26 +01:00
adator
2e422c93e5 Base.py refactoring. Nothing fancy. 2025-11-16 13:35:09 +01:00
adator
ba989b7f26 Update debug message. 2025-11-16 13:22:33 +01:00
adator
fc01de34b2 Adding new debug messages when module exist or not 2025-11-16 13:21:57 +01:00
adator
a3dcc20a06 Handle SETHOST response to update the vhost of the user 2025-11-16 13:20:11 +01:00
adator
7ffc58d4ff Trigger a thread clean-up before running a new thread 2025-11-16 13:15:49 +01:00
adator
6a0d4e2286 Updating some translation, refactoring the code! 2025-11-11 03:49:16 +01:00
adator
7dd15f2dac Updating translation! 2025-11-11 03:48:37 +01:00
adator
10cad7cda6 Refactoring code! 2025-11-11 03:48:20 +01:00
adator
999072a88a Refactoring unreal6 code! 2025-11-11 03:47:02 +01:00
adator
8932e1441a Update docstring of the test module. 2025-11-10 23:38:19 +01:00
adator
9cee758b6f Remove unused imported library! 2025-11-10 23:13:17 +01:00
adator
511e0c0715 Introduce MOD_HEADER in all modules. impact modules.py, definition.py, unreal6 protocol. 2025-11-10 23:09:13 +01:00
adator
371c8fb5f1 Exclude modules.txt files from the commit 2025-11-10 23:08:18 +01:00
adator
401e785383 Remove deprecated class (abstractproperty). 2025-11-10 00:15:53 +01:00
adator
a7efede75e Introduce MOD_HEADER constante in all modules as mandatory constante. 2025-11-10 00:13:35 +01:00
adator
a1254c7a39 Refactoring the Code, Dispatch server responses to all modules including UID, avoid multi calls to get_user, get_nickname... 2025-11-09 23:39:19 +01:00
adator
c05990f862 Refactoring the Code! 2025-11-09 23:37:42 +01:00
adator
de2b5fa8e2 Refactoring the Code, comment the code to dispatch the server response to all modules 2025-11-09 23:36:58 +01:00
adator
371645149d Refactoring the Code, clean unsused methods. avoid multi calls to get_user, get_nickname ... 2025-11-09 23:35:27 +01:00
adator
a6cf11ae2a Update the log level when userobj is not found! 2025-11-09 23:33:17 +01:00
adator
445cbc27b0 exclude users.txt file 2025-11-09 23:32:21 +01:00
adator
f9eb374798 When fp is None, return False. log when login via fingerprint. 2025-11-09 20:53:30 +01:00
adator
17cb2ada5f Fix server response, fix ircd parser aswell, update first setup on base.py 2025-11-08 21:21:38 +01:00
adator
b52a57f95a Fix library path in settings.py 2025-11-07 22:46:09 +01:00
adator
1bfd95c291 refactoring code 2025-11-06 20:12:28 +01:00
adator
0c6c3cd6ac Refactoring the code, create new folder modules. 2025-11-02 22:52:27 +01:00
adator
0e6384c26c modify and move protocol interface to interfaces folder. refactoring all dependencies 2025-11-02 22:21:55 +01:00
adator
79c1b94a92 Update parse_quit, now it returns MUser object and the reason. 2025-11-02 21:28:44 +01:00
adator
5a1432c1e6 Update parse_uid, now it returns MUser object. 2025-11-02 21:17:15 +01:00
adator
34b5b4204e Update parse_privmsg, now it returns sender, reciever, channel objects and the message 2025-11-02 20:58:56 +01:00
adator
ff58cbb022 Adding serveur_protocol to the configuration exemple. 2025-11-02 00:16:42 +01:00
adator
6450418859 Update docstring 2025-11-02 00:16:04 +01:00
adator
9f2da13f88 Unload the module when the protocol is not unreal6 2025-11-02 00:15:43 +01:00
adator
0117e1dd3a Update the protocol inspircd to work with the protocol interfaces. 2025-11-02 00:14:36 +01:00
adator
deb76baf30 Update the version of defender 6.3.2>>6.3.3 2025-11-01 22:24:57 +01:00
adator
29f049b3c3 Update the protocol interface, no more __init__ constructor needed in the child class! 2025-11-01 22:20:49 +01:00
adator
fb41a13d0a Move mod_clone to use the module interface. 2025-11-01 22:11:15 +01:00
adator
769ab8b632 update rest of modules to fit requirements 2025-11-01 22:00:08 +01:00
adator
2fbe75b83e update module management 2025-11-01 17:39:16 +01:00
adator
8abae5df3e fix db_patch, the table name is no more hard coded! 2025-11-01 15:57:46 +01:00
adator
1a71a6eb4d 1st version of module interface! 2025-10-30 00:35:50 +01:00
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
53 changed files with 4391 additions and 2494 deletions

8
.gitignore vendored
View File

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

65
Makefile Normal file
View File

@@ -0,0 +1,65 @@
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)
@export echo $DBUS_SESSION_BUS_ADDRESS && \
systemctl --user stop defender
$(info Defender has been stopped...)
@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
```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
# 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
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:
```bash
# Bash
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git
$ cd IRC_DEFENDER_MODULES
$ git clone https://github.com/adator85/DEFENDER.git defender
$ cd defender/
$ python3 -m venv .pyenv
$ 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"
# 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")
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
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
```json
{
"SERVEUR_IP": "IP.DE.TON.SERVER",
"SERVEUR_HOSTNAME": "HOST.DE.TON.SERVER",
"SERVEUR_LINK": "LINK.DE.TON.SERVER",
"SERVEUR_PORT": 6901,
"SERVEUR_PASSWORD": "MOT_DE_PASS_DE_TON_LINK",
"SERVEUR_ID": "10Z",
"SERVEUR_SSL": true,
```yaml
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_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"
}
OWNER: "TON_NICK_NAME"
PASSWORD: "TON_PASSWORD"
```
## Exemple complet de configuration
```json
{
"SERVEUR_IP": "YOUR.SERVER.IP",
"SERVEUR_HOSTNAME": "YOUR.SERVER.HOST",
"SERVEUR_LINK": "LINK.DE.TON.SERVER",
"SERVEUR_PORT": 6901,
"SERVEUR_PASSWORD": "YOUR_LINK_PASSWORD",
"SERVEUR_ID": "10Z",
"SERVEUR_SSL": true,
```yaml
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": "!",
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",
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",
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",
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",
CLONE_CHANNEL: "#clones"
CLONE_CMODES: "+nts"
CLONE_LOG_HOST_EXEMPT: ["HOST.TO.SKIP"]
CLONE_CHANNEL_PASSWORD: "YOUR_CHANNEL_PASSWORD"
"API_TIMEOUT": 2,
API_TIMEOUT: 2
"PORTS_TO_SCAN": [3028, 8080, 1080, 1085, 4145, 9050],
"WHITELISTED_IP": ["127.0.0.1"],
"GLINE_DURATION": "30",
PORTS_TO_SCAN: [3028 8080 1080 1085 4145 9050]
WHITELISTED_IP: ["127.0.0.1"]
GLINE_DURATION: "30"
"DEBUG_LEVEL": 20
}
DEBUG_LEVEL: 20
DEBUG_HARD: true
```
# \\!/ 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.
a votre premiere connexion vous devez tapez
```
/msg [NomDuService] auth [nickname] [password]
/msg [NomDuService] firstauth [nickname] [password]
-- Une fois identifié tapez la commande suivante
/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,48 @@
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
SERVEUR_PROTOCOL: "unreal6" # unreal6 or inspircd
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", "192.168.1.1"]
GLINE_DURATION: "30"
DEBUG_LEVEL: 20
DEBUG_HARD: true

View File

@@ -1,16 +1,12 @@
import importlib
import os
import re
import json
import sys
import time
import socket
import threading
import ipaddress
import ast
import requests
from pathlib import Path
from types import ModuleType
from dataclasses import fields
from typing import Any, Optional, TYPE_CHECKING
from base64 import b64decode, b64encode
@@ -30,7 +26,8 @@ class Base:
self.Utils = loader.Utils
self.logs = loader.Logs
self.check_for_new_version(True) # Verifier si une nouvelle version est disponible
# Check if new Defender version is available
self.check_for_new_version(True)
# Liste des timers en cours
self.running_timers: list[threading.Timer] = self.Settings.RUNNING_TIMERS
@@ -47,9 +44,17 @@ class Base:
# Création du lock
self.lock = self.Settings.LOCK
self.install: bool = False # Initialisation de la variable d'installation
self.engine, self.cursor = self.db_init() # Initialisation de la connexion a la base de données
self.__create_db() # Initialisation de la base de données
# Init install variable
self.install: bool = False
# Init database connection
self.engine, self.cursor = self.db_init()
# Create the database
# self.__create_db()
def init(self) -> None:
self.__create_db()
def __set_current_defender_version(self) -> None:
"""This will put the current version of Defender
@@ -296,13 +301,14 @@ class Base:
'password': password,
'hostname': '*',
'vhost': '*',
'language': 'EN',
'level': 5
}
self.db_execute_query(f"""
INSERT INTO {self.Config.TABLE_ADMIN}
(createdOn, user, password, hostname, vhost, level)
(createdOn, user, password, hostname, vhost, language, level)
VALUES
(:createdOn, :user, :password, :hostname, :vhost, :level)"""
(:createdOn, :user, :password, :hostname, :vhost, :language, :level)"""
, mes_donnees)
return None
@@ -324,7 +330,7 @@ class Base:
self.logs.error(f'Assertion Error -> {ae}')
return None
def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False, daemon: bool = True) -> None:
def create_thread(self, func: object, func_args: tuple = (), run_once: bool = False, daemon: bool = True) -> None:
"""Create a new thread and store it into running_threads variable
Args:
@@ -333,6 +339,9 @@ class Base:
run_once (bool, optional): If you want to ensure that this method/function run once. Defaults to False.
"""
try:
# Clean unused threads first
self.garbage_collector_thread()
func_name = func.__name__
if run_once:
@@ -346,8 +355,8 @@ class Base:
self.running_threads.append(th)
self.logs.debug(f"-- Thread ID : {str(th.ident)} | Thread name : {th.name} | Running Threads : {len(threading.enumerate())}")
except AssertionError as ae:
self.logs.error(f'{ae}')
except Exception as err:
self.logs.error(err, exc_info=True)
def is_thread_alive(self, thread_name: str) -> bool:
"""Check if the thread is still running! using the is_alive method of Threads.
@@ -424,11 +433,11 @@ class Base:
if thread.name != 'heartbeat':
if not thread.is_alive():
self.running_threads.remove(thread)
self.logs.info(f"-- Thread {str(thread.name)} {str(thread.native_id)} removed")
self.logs.debug(f"-- Thread {str(thread.name)} {str(thread.native_id)} has been removed!")
# print(threading.enumerate())
except AssertionError as ae:
self.logs.error(f'Assertion Error -> {ae}')
except Exception as err:
self.logs.error(err, exc_info=True)
def garbage_collector_sockets(self) -> None:
@@ -484,7 +493,7 @@ class Base:
engine = create_engine(f'sqlite:///{full_path_db}.db', echo=False)
cursor = engine.connect()
self.logs.info("-- database connexion has been initiated")
self.logs.info("-- Database connexion has been initiated")
return engine, cursor
def __create_db(self) -> None:
@@ -538,6 +547,7 @@ class Base:
vhost TEXT,
password TEXT,
fingerprint TEXT,
language TEXT,
level INTEGER
)
'''
@@ -598,8 +608,10 @@ class Base:
def db_patch(self, table_name: str, column_name: str, column_type: str) -> bool:
if not self.db_is_column_exist(table_name, column_name):
patch = f"ALTER TABLE {self.Config.TABLE_ADMIN} ADD COLUMN {column_name} {column_type}"
patch = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"
update_row = f"UPDATE {table_name} SET language = 'EN' WHERE language is null"
self.db_execute_query(patch)
self.db_execute_query(update_row)
self.logs.debug(f"The patch has been applied")
self.logs.debug(f"Table name: {table_name}, Column name: {column_name}, Column type: {column_type}")
return True
@@ -680,14 +692,13 @@ class Base:
return False
def decode_ip(self, ip_b64encoded: str) -> Optional[str]:
binary_ip = b64decode(ip_b64encoded)
try:
binary_ip = b64decode(ip_b64encoded)
decoded_ip = ipaddress.ip_address(binary_ip)
return decoded_ip.exploded
except ValueError as ve:
self.logs.critical(f'This remote ip is not valid : {ve}')
self.logs.critical(f'This remote ip ({ip_b64encoded}) is not valid : {ve}')
return None
def encode_ip(self, remote_ip_address: str) -> Optional[str]:

View File

@@ -1,64 +0,0 @@
from json import load
from sys import exit
from os import sep
from typing import Any, Optional, Union, TYPE_CHECKING
from core.definition import MConfig
if TYPE_CHECKING:
from core.loader import Loader
class Configuration:
def __init__(self, loader: 'Loader') -> None:
self.Loader = loader
self.Logs = loader.Logs
self._config_model: MConfig = self.__load_service_configuration()
loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD)
return None
def get_config_model(self) -> MConfig:
return self._config_model
def __load_json_service_configuration(self) -> Optional[dict[str, Any]]:
try:
conf_filename = f'config{sep}configuration.json'
with open(conf_filename, 'r') as configuration_data:
configuration: dict[str, Union[str, int, list, dict]] = load(configuration_data)
return configuration
except FileNotFoundError as fe:
self.Logs.error(f'FileNotFound: {fe}')
self.Logs.error('Configuration file not found please create config/configuration.json')
exit(0)
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:
try:
import_config = self.__load_json_service_configuration()
Model_keys = MConfig().to_dict()
model_key_list: list = []
json_config_key_list: list = []
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!")
return MConfig(**import_config)
except TypeError as te:
self.Logs.error(te)

View File

@@ -0,0 +1,125 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Optional
from dataclasses import dataclass
if TYPE_CHECKING:
from core.irc import Irc
class IModule(ABC):
@abstractmethod
@dataclass
class ModConfModel:
"""The Model containing the module parameters
"""
def __init__(self, uplink: 'Irc') -> None:
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory)
self.Irc = uplink
# Add Loader object to the module (Mandatory)
self.Loader = uplink.Loader
# Add Protocol to the module (Mandatory)
self.Protocol = uplink.Protocol
# Add Global Configuration to the module (Mandatory)
self.Config = uplink.Config
# Add Settings to the module (Mandatory)
self.Settings = uplink.Settings
# Add Base object to the module (Mandatory)
self.Base = uplink.Base
# Add Main Utils (Mandatory)
self.MainUtils = uplink.Utils
# Add logs object to the module (Mandatory)
self.Logs = uplink.Loader.Logs
# Add User object to the module (Mandatory)
self.User = uplink.User
# Add Client object to the module (Mandatory)
self.Client = uplink.Client
# Add Admin object to the module (Mandatory)
self.Admin = uplink.Admin
# Add Channel object to the module (Mandatory)
self.Channel = uplink.Channel
# Add Reputation object to the module (Optional)
self.Reputation = uplink.Reputation
# Load the child classes
self.load()
# Inspect child classes
self.inspect_class()
self.create_tables()
# Sync the configuration with core configuration (Mandatory)
uplink.Base.db_sync_core_config(self.module_name, self.ModConfig)
# Log the module
self.Logs.debug(f'Loading Module {self.module_name} ...')
def update_configuration(self, param_key: str, param_value: str) -> None:
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
def inspect_class(self):
if not hasattr(self, 'ModConfig'):
raise AttributeError("The Module must init ModConfig attribute in the load method!")
if not hasattr(self, 'MOD_HEADER'):
raise NotImplementedError(f"You must declare the header of the module in {self.__class__.__name__}!")
@abstractmethod
def create_tables(self) -> None:
"""Method that will create the database if it does not exist.
A single Session for this class will be created, which will be used within this class/module.
Returns:
None: No return is expected
"""
@abstractmethod
def load(self) -> None:
"""This method is executed when the module is loaded or reloaded.
"""
@abstractmethod
def unload(self) -> None:
"""This method is executed when the module is unloaded or reloaded.
"""
@abstractmethod
def cmd(self, data: list) -> None:
"""When recieving server messages.
Args:
data (list): The recieved message
"""
@abstractmethod
def hcmds(self, user: str, channel: Optional[str], cmd: list[str], fullcmd: Optional[list[str]] = None) -> None:
"""These are the commands recieved from a client
Args:
user (str): The client
channel (str|None): The channel if available
cmd (list): The user command sent
fullcmd (list, optional): The full server message. Defaults to [].
"""

View File

@@ -0,0 +1,576 @@
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, MUser, MChannel
from core.irc import Irc
class IProtocol(ABC):
Handler: Optional[CommandHandler] = None
def __init__(self, uplink: 'Irc'):
self.name: Optional[str] = None
self.protocol_version: int = -1
self.known_protocol: set[str] = set()
self._Irc = uplink
self._Config = uplink.Config
self._Base = uplink.Base
self._Settings = uplink.Base.Settings
self._Utils = uplink.Loader.Utils
self._Logs = uplink.Loader.Logs
self._User = uplink.User
self._Channel = uplink.Channel
self.Handler = CommandHandler(uplink.Loader)
self.init_protocol()
self._Logs.info(f"[PROTOCOL] Protocol [{self.__class__.__name__}] loaded!")
@abstractmethod
def init_protocol(self):
"""Init protocol
"""
@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, server_msg: list[str]) -> Optional['MUser']:
"""Parse UID and return dictionary.
Args:
server_msg (list[str]): The UID IRCD message
Returns:
Optional[MUser]: The MUser object or None
"""
@abstractmethod
def parse_quit(self, server_msg: list[str]) -> tuple[Optional['MUser'], str]:
"""Parse quit and return dictionary.
>>> [':97KAAAAAB', 'QUIT', ':Quit:', 'this', 'is', 'my', 'reason', 'to', 'quit']
Args:
server_msg (list[str]): The server message to parse
Returns:
tuple[MUser, str]: The User Who Quit Object and the reason.
"""
@abstractmethod
def parse_nick(self, server_msg: list[str]) -> tuple[Optional['MUser'], str, str]:
"""Parse nick changes and return dictionary.
>>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740']
Args:
server_msg (list[str]): The server message to parse
Returns:
tuple(MUser, newnickname(str), timestamp(str)): Tuple of the response.
>>> MUser, newnickname, timestamp
"""
@abstractmethod
def parse_privmsg(self, server_msg: list[str]) -> tuple[Optional['MUser'], Optional['MUser'], Optional['MChannel'], str]:
"""Parse PRIVMSG message.
>>> [':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message']
Args:
server_msg (list[str]): The server message to parse
Returns:
tuple[MUser(Sender), MUser(Reciever), MChannel, str]: Sender user model, reciever user model, Channel model, messgae.
"""
# ------------------------------------------------------------------------
# EVENT HANDLER
# ------------------------------------------------------------------------
@abstractmethod
def on_svs2mode(self, server_msg: list[str]) -> None:
"""Handle svs2mode coming from a server
>>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r']
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_mode(self, server_msg: list[str]) -> None:
"""Handle mode coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_umode2(self, server_msg: list[str]) -> None:
"""Handle umode2 coming from a server
>>> [':adator_', 'UMODE2', '-i']
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_quit(self, server_msg: list[str]) -> None:
"""Handle quit coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_squit(self, server_msg: list[str]) -> None:
"""Handle squit coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_protoctl(self, server_msg: list[str]) -> None:
"""Handle protoctl coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_nick(self, server_msg: list[str]) -> None:
"""Handle nick coming from a server
new nickname
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_sjoin(self, server_msg: list[str]) -> None:
"""Handle sjoin coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_part(self, server_msg: list[str]) -> None:
"""Handle part coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_eos(self, server_msg: list[str]) -> None:
"""Handle EOS coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_reputation(self, server_msg: list[str]) -> None:
"""Handle REPUTATION coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_uid(self, server_msg: list[str]) -> None:
"""Handle uid message coming from the server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_privmsg(self, server_msg: list[str]) -> None:
"""Handle PRIVMSG message coming from the server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_server_ping(self, server_msg: list[str]) -> None:
"""Send a PONG message to the server
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
def on_server(self, server_msg: list[str]) -> None:
"""_summary_
Args:
server_msg (list[str]): _description_
"""
@abstractmethod
def on_version(self, server_msg: list[str]) -> None:
"""Sending Server Version to the server
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
def on_time(self, server_msg: list[str]) -> None:
"""Sending TIME answer to a requestor
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
def on_ping(self, server_msg: list[str]) -> None:
"""Sending a PING answer to requestor
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
def on_version_msg(self, server_msg: list[str]) -> None:
"""Handle version coming from the server
\n ex. /version Defender
Args:
server_msg (list[str]): Original message from the server
"""
@abstractmethod
def on_smod(self, server_msg: list[str]) -> None:
"""Handle SMOD message coming from the server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
def on_sasl(self, server_msg: list[str]) -> Optional['MSasl']:
"""Handle SASL coming from a server
Args:
server_msg (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, server_msg: list[str]) -> None:
"""Handle MD responses
[':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...']
Args:
server_msg (list[str]): The server reply
"""
@abstractmethod
def on_kick(self, server_msg: list[str]) -> None:
"""When a user is kicked out from a channel
Eg. ['@unrealircd.org...', ':001', 'KICK', '#jsonrpc', '001ELW13T', ':Kicked', 'from', 'JSONRPC', 'User']
Args:
server_msg (list[str]): The server message
"""
@abstractmethod
def on_sethost(self, server_msg: list[str]) -> None:
"""On SETHOST command
>>> [':001DN7305', 'SETHOST', ':netadmin.example.org']
Args:
server_msg (list[str]): _description_
"""

View File

@@ -9,7 +9,17 @@ class Admin:
UID_ADMIN_DB: list[MAdmin] = []
def __init__(self, loader: 'Loader') -> None:
"""
Args:
loader (Loader): The Loader Instance.
"""
self.Logs = loader.Logs
self.Base = loader.Base
self.Setting = loader.Settings
self.Config = loader.Config
self.User = loader.User
self.Definition = loader.Definition
def insert(self, new_admin: MAdmin) -> bool:
"""Insert a new admin object model
@@ -83,18 +93,11 @@ class Admin:
Returns:
bool: True if the admin has been deleted
"""
for record in self.UID_ADMIN_DB:
if record.uid == uidornickname:
# If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record)
self.Logs.debug(f'UID ({record.uid}) has been deleted')
return True
if record.nickname.lower() == uidornickname.lower():
# If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record)
self.Logs.debug(f'nickname ({record.nickname}) has been deleted')
return True
admin_obj = self.get_admin(uidornickname)
if admin_obj:
self.UID_ADMIN_DB.remove(admin_obj)
self.Logs.debug(f'UID ({admin_obj.uid}) has been deleted')
return True
self.Logs.debug(f'The UID {uidornickname} was not deleted')
@@ -152,4 +155,70 @@ class Admin:
if record.uid == uidornickname:
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
"""
if fp is None:
return False
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
self.Logs.debug(f"[Fingerprint login] {user_obj.nickname} ({admin_obj.account}) has been logged in successfully!")
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)
"""
def __init__(self, loader: 'Loader') -> None:
def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader Instance
"""
self.Logs = loader.Logs
self.Base = loader.Base
self.Utils = loader.Utils
return None
def insert(self, new_channel: 'MChannel') -> bool:
"""This method will insert a new channel and if the channel exist it will update the user list (uids)
@@ -110,6 +112,7 @@ class Channel:
return result
except ValueError as ve:
self.Logs.error(f'{ve}')
return False
def delete_user_from_all_channel(self, uid:str) -> bool:
"""Delete a client from all channels
@@ -134,6 +137,7 @@ class Channel:
return result
except ValueError as ve:
self.Logs.error(f'{ve}')
return False
def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool:
"""Add a client to a channel
@@ -226,16 +230,18 @@ class Channel:
return False
pattern = fr'^#'
isChannel = findall(pattern, channel_to_check)
is_channel = findall(pattern, channel_to_check)
if not isChannel:
if not is_channel:
return False
else:
return True
except TypeError as te:
self.Logs.error(f'TypeError: [{channel_to_check}] - {te}')
return False
except Exception as err:
self.Logs.error(f'Error Not defined: {err}')
return False
def db_query_channel(self, action: Literal['add','del'], module_name: str, channel_name: str) -> bool:
"""You can add a channel or delete a channel.
@@ -282,8 +288,7 @@ class Channel:
else:
return False
case _:
return False
except Exception as err:
self.Logs.error(err)
return False

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
import sys
import yaml
from json import load
from sys import exit
from os import sep
from typing import Any, Optional, Union, TYPE_CHECKING
from core.definition import MConfig
if TYPE_CHECKING:
from core.loader import Loader
class Configuration:
def __init__(self, loader: 'Loader') -> None:
self.Loader = loader
self.Logs = loader.Logs
self.configuration_model = self.__load_service_configuration()
loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD)
return None
@property
def configuration_model(self) -> MConfig:
return self._config_model
@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:
conf_filename = f'config{sep}configuration.yaml'
with open(conf_filename, 'r') as conf:
configuration: dict[str, dict[str, Any]] = yaml.safe_load(conf)
return configuration.get('configuration', None)
except FileNotFoundError as fe:
self.Logs.error(f'FileNotFound: {fe}')
self.Logs.error('Configuration file not found please create config/configuration.yaml')
exit("Configuration file not found please create config/configuration.yaml")
def __load_service_configuration(self) -> MConfig:
try:
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")
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()]
for key_to_remove in list_key_to_remove:
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 [!]")
self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!")
return MConfig(**import_config)
except TypeError as te:
self.Logs.error(te)

View File

@@ -3,7 +3,6 @@ import sys
import time
from typing import TYPE_CHECKING
import socket
from core.classes.protocol import Protocol
if TYPE_CHECKING:
from core.irc import Irc
@@ -12,35 +11,34 @@ if TYPE_CHECKING:
REHASH_MODULES = [
'core.definition',
'core.utils',
'core.classes.config',
'core.classes.modules.config',
'core.base',
'core.classes.commands',
'core.classes.modules.commands',
'core.classes.modules.rpc',
'core.classes.interfaces.iprotocol',
'core.classes.interfaces.imodule',
'core.classes.protocols.command_handler',
'core.classes.protocols.factory',
'core.classes.protocols.unreal6',
'core.classes.protocols.inspircd',
'core.classes.protocol'
'core.classes.protocols.inspircd'
]
def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -> None:
"""
Args:
uplink (Irc): The Irc instance
reason (str): The reason of the restart.
"""
# reload modules.
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
uplink.ModuleUtils.unload_one_module(uplink, module.module_name)
uplink.ModuleUtils.model_clear() # Clear loaded modules.
uplink.User.UID_DB.clear() # Clear User Object
uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object
uplink.Client.CLIENT_DB.clear() # Clear Client object
uplink.Base.garbage_collector_thread()
# Reload configuration
uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model()
uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader)
uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol
uplink.Logs.debug(f'[{uplink.Config.SERVICE_NICKNAME} RESTART]: Reloading configuration!')
uplink.Protocol.send_squit(server_id=uplink.Config.SERVEUR_ID, server_link=uplink.Config.SERVEUR_LINK, reason="Defender Power off")
uplink.Protocol.send_squit(server_id=uplink.Config.SERVEUR_ID, server_link=uplink.Config.SERVEUR_LINK, reason=reason)
uplink.Logs.debug('Restarting Defender ...')
uplink.IrcSocket.shutdown(socket.SHUT_RDWR)
uplink.IrcSocket.close()
@@ -49,16 +47,31 @@ def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -
time.sleep(0.5)
uplink.Logs.warning('-- Waiting for socket to close ...')
# Reload configuration
uplink.Loader.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).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.ModuleUtils.model_clear() # Clear loaded modules.
uplink.User.UID_DB.clear() # Clear User Object
uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object
uplink.Client.CLIENT_DB.clear() # Clear Client object
uplink.init_service_user()
uplink.Utils.create_socket(uplink)
uplink.Protocol.send_link()
uplink.join_saved_channels()
uplink.ModuleUtils.db_load_all_existing_modules(uplink)
uplink.Config.DEFENDER_RESTART = 0
def rehash_service(uplink: 'Irc', nickname: str) -> None:
need_a_restart = ["SERVEUR_ID"]
uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS)
uplink.Loader.RpcServer.stop_server()
restart_flag = False
config_model_bakcup = uplink.Config
mods = REHASH_MODULES
@@ -69,8 +82,8 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None:
msg=f'[REHASH] Module [{mod}] reloaded',
channel=uplink.Config.SERVICE_CHANLOG
)
uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model()
uplink.Utils = sys.modules['core.utils']
uplink.Config = uplink.Loader.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).configuration_model
uplink.Config.HSID = config_model_bakcup.HSID
uplink.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT
uplink.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART
@@ -103,10 +116,13 @@ def rehash_service(uplink: 'Irc', nickname: str) -> None:
# Reload Main Commands Module
uplink.Commands = uplink.Loader.CommandModule.Command(uplink.Loader)
uplink.Loader.RpcServer = uplink.Loader.RpcServerModule.JSONRPCServer(uplink.Loader)
uplink.Loader.RpcServer.start_server()
uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('db_commands')
uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader)
uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol
uplink.Loader.Base = uplink.Loader.BaseModule.Base(uplink.Loader)
uplink.Protocol = uplink.Loader.PFactory.get()
uplink.Protocol.register_command()
# Reload Service modules
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():

View File

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

View File

@@ -0,0 +1,240 @@
import base64
import json
import logging
from enum import Enum
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import TYPE_CHECKING, Any, Optional
from core.classes.modules.rpc.rpc_user import RPCUser
from core.classes.modules.rpc.rpc_channel import RPCChannel
from core.classes.modules.rpc.rpc_command import RPCCommand
if TYPE_CHECKING:
from core.loader import Loader
ProxyLoader: Optional['Loader'] = None
class RPCRequestHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
pass
def do_POST(self):
logs = ProxyLoader.Logs
self.server_version = 'Defender6'
self.sys_version = ProxyLoader.Config.CURRENT_VERSION
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
request_data: dict = json.loads(body)
rip, rport = self.client_address
if not self.authenticate(request_data):
return None
response_data = {
'jsonrpc': '2.0',
'id': request_data.get('id', 123)
}
method = request_data.get("method")
params: dict[str, Any] = request_data.get("params", {})
response_data['method'] = method
http_code = 200
match method:
case 'user.list':
user = RPCUser(ProxyLoader)
response_data['result'] = user.user_list()
logs.debug(f'[RPC] {method} recieved from {rip}:{rport}')
del user
case 'user.get':
user = RPCUser(ProxyLoader)
uid_or_nickname = params.get('uid_or_nickname', None)
response_data['result'] = user.user_get(uid_or_nickname)
logs.debug(f'[RPC] {method} recieved from {rip}:{rport}')
del user
case 'channel.list':
channel = RPCChannel(ProxyLoader)
response_data['result'] = channel.channel_list()
logs.debug(f'[RPC] {method} recieved from {rip}:{rport}')
del channel
case 'command.list':
command = RPCCommand(ProxyLoader)
response_data['result'] = command.command_list()
logs.debug(f'[RPC] {method} recieved from {rip}:{rport}')
del command
case 'command.get.by.module':
command = RPCCommand(ProxyLoader)
module_name = params.get('name', None)
response_data['result'] = command.command_get_by_module(module_name)
logs.debug(f'[RPC] {method} recieved from {rip}:{rport}')
del command
case 'command.get.by.name':
command = RPCCommand(ProxyLoader)
command_name = params.get('name', None)
response_data['result'] = command.command_get_by_name(command_name)
logs.debug(f'[RPC] {method} recieved from {rip}:{rport}')
del command
case _:
response_data['error'] = create_error_response(JSONRPCErrorCode.METHOD_NOT_FOUND)
logs.debug(f'[RPC ERROR] {method} recieved from {rip}:{rport}')
http_code = 404
self.send_response(http_code)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(response_data).encode('utf-8'))
return None
def do_GET(self):
self.server_version = 'Defender6'
self.sys_version = ProxyLoader.Config.CURRENT_VERSION
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
request_data: dict = json.loads(body)
if not self.authenticate(request_data):
return None
response_data = {'jsonrpc': '2.0', 'id': request_data.get('id', 321),
'error': create_error_response(JSONRPCErrorCode.INVALID_REQUEST)}
self.send_response(404)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(response_data).encode('utf-8'))
return None
def authenticate(self, request_data: dict) -> bool:
logs = ProxyLoader.Logs
auth = self.headers.get('Authorization', None)
if auth is None:
self.send_auth_error(request_data)
return False
# Authorization header format: Basic base64(username:password)
auth_type, auth_string = auth.split(' ', 1)
if auth_type.lower() != 'basic':
self.send_auth_error(request_data)
return False
try:
# Decode the base64-encoded username:password
decoded_credentials = base64.b64decode(auth_string).decode('utf-8')
username, password = decoded_credentials.split(":", 1)
# Check the username and password.
for rpcuser in ProxyLoader.Irc.Config.RPC_USERS:
if rpcuser.get('USERNAME', None) == username and rpcuser.get('PASSWORD', None) == password:
return True
self.send_auth_error(request_data)
return False
except Exception as e:
self.send_auth_error(request_data)
logs.error(e)
return False
def send_auth_error(self, request_data: dict) -> None:
response_data = {
'jsonrpc': '2.0',
'id': request_data.get('id', 123),
'error': create_error_response(JSONRPCErrorCode.AUTHENTICATION_ERROR)
}
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm="Authorization Required"')
self.end_headers()
self.wfile.write(json.dumps(response_data).encode('utf-8'))
class JSONRPCServer:
def __init__(self, loader: 'Loader'):
global ProxyLoader
ProxyLoader = loader
self._Loader = loader
self._Base = loader.Base
self._Logs = loader.Logs
self.rpc_server: Optional[HTTPServer] = None
self.connected: bool = False
def start_server(self, server_class=HTTPServer, handler_class=RPCRequestHandler, *, hostname: str = 'localhost', port: int = 5000):
logging.getLogger('http.server').setLevel(logging.CRITICAL)
server_address = (hostname, port)
self.rpc_server = server_class(server_address, handler_class)
self._Logs.debug(f"Server ready on http://{hostname}:{port}...")
self._Base.create_thread(self.thread_start_rpc_server, (), True)
def thread_start_rpc_server(self) -> None:
self._Loader.Irc.Protocol.send_priv_msg(
self._Loader.Config.SERVICE_NICKNAME, "Defender RPC Server has started successfuly!", self._Loader.Config.SERVICE_CHANLOG
)
self.connected = True
self.rpc_server.serve_forever()
ProxyLoader.Logs.debug(f"RPC Server down!")
def stop_server(self):
self._Base.create_thread(self.thread_stop_rpc_server)
def thread_stop_rpc_server(self):
self.rpc_server.shutdown()
ProxyLoader.Logs.debug(f"RPC Server shutdown!")
self.rpc_server.server_close()
ProxyLoader.Logs.debug(f"RPC Server clean-up!")
self._Base.garbage_collector_thread()
self._Loader.Irc.Protocol.send_priv_msg(
self._Loader.Config.SERVICE_NICKNAME, "Defender RPC Server has stopped successfuly!", self._Loader.Config.SERVICE_CHANLOG
)
self.connected = False
class JSONRPCErrorCode(Enum):
PARSE_ERROR = -32700 # Syntax error in the request (malformed JSON)
INVALID_REQUEST = -32600 # Invalid Request (incorrect structure or missing fields)
METHOD_NOT_FOUND = -32601 # Method not found (the requested method does not exist)
INVALID_PARAMS = -32602 # Invalid Params (the parameters provided are incorrect)
INTERNAL_ERROR = -32603 # Internal Error (an internal server error occurred)
# Custom application-specific errors (beyond standard JSON-RPC codes)
CUSTOM_ERROR = 1001 # Custom application-defined error (e.g., user not found)
AUTHENTICATION_ERROR = 1002 # Authentication failure (e.g., invalid credentials)
PERMISSION_ERROR = 1003 # Permission error (e.g., user does not have access to this method)
RESOURCE_NOT_FOUND = 1004 # Resource not found (e.g., the requested resource does not exist)
DUPLICATE_REQUEST = 1005 # Duplicate request (e.g., a similar request has already been processed)
def description(self):
"""Returns a description associated with each error code"""
descriptions = {
JSONRPCErrorCode.PARSE_ERROR: "The JSON request is malformed.",
JSONRPCErrorCode.INVALID_REQUEST: "The request is invalid (missing or incorrect fields).",
JSONRPCErrorCode.METHOD_NOT_FOUND: "The requested method could not be found.",
JSONRPCErrorCode.INVALID_PARAMS: "The parameters provided are invalid.",
JSONRPCErrorCode.INTERNAL_ERROR: "An internal error occurred on the server.",
JSONRPCErrorCode.CUSTOM_ERROR: "A custom error defined by the application.",
JSONRPCErrorCode.AUTHENTICATION_ERROR: "User authentication failed.",
JSONRPCErrorCode.PERMISSION_ERROR: "User does not have permission to access this method.",
JSONRPCErrorCode.RESOURCE_NOT_FOUND: "The requested resource could not be found.",
JSONRPCErrorCode.DUPLICATE_REQUEST: "The request is a duplicate or is already being processed.",
}
return descriptions.get(self, "Unknown error")
def create_error_response(error_code: JSONRPCErrorCode, details: dict = None) -> dict[str, str]:
"""Create a JSON-RPC error!"""
response = {
"code": error_code.value,
"message": error_code.description(),
}
if details:
response["data"] = details
return response

View File

@@ -0,0 +1,12 @@
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from core.loader import Loader
class RPCChannel:
def __init__(self, loader: 'Loader'):
self._Loader = loader
self._Channel = loader.Channel
def channel_list(self) -> list[dict]:
return [chan.to_dict() for chan in self._Channel.UID_CHANNEL_DB]

View File

@@ -0,0 +1,21 @@
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from core.loader import Loader
class RPCCommand:
def __init__(self, loader: 'Loader'):
self._Loader = loader
self._Command = loader.Commands
def command_list(self) -> list[dict]:
return [command.to_dict() for command in self._Command.DB_COMMANDS]
def command_get_by_module(self, module_name: str) -> list[dict]:
return [command.to_dict() for command in self._Command.DB_COMMANDS if command.module_name.lower() == module_name.lower()]
def command_get_by_name(self, command_name: str) -> dict:
for command in self._Command.DB_COMMANDS:
if command.command_name.lower() == command_name.lower():
return command.to_dict()
return {}

View File

@@ -0,0 +1,30 @@
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from core.loader import Loader
from core.definition import MUser
class RPCUser:
def __init__(self, loader: 'Loader'):
self._Loader = loader
self._User = loader.User
def user_list(self) -> list[dict]:
users = self._User.UID_DB.copy()
copy_users: list['MUser'] = []
for user in users:
copy_user = user.copy()
copy_user.connexion_datetime = copy_user.connexion_datetime.strftime('%d-%m-%Y')
copy_users.append(copy_user)
return [user.to_dict() for user in copy_users]
def user_get(self, uidornickname: str) -> Optional[dict]:
user = self._User.get_user(uidornickname)
if user:
user_copy = user.copy()
user_copy.connexion_datetime = user_copy.connexion_datetime.strftime('%d-%m-%Y')
return user_copy.to_dict()
return None

View File

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

View File

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

View File

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

View File

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

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 ..interfaces.iprotocol 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
from datetime import datetime
from json import dumps
from dataclasses import dataclass, field, asdict, fields
from dataclasses import dataclass, field, asdict, fields, replace
from typing import Literal, Any, Optional
from os import sep
@@ -14,6 +14,10 @@ class MainModel:
def to_json(self) -> str:
"""Return the object of a dataclass a json str."""
return dumps(self.to_dict())
def copy(self):
"""Return the object of a dataclass a json str."""
return replace(self)
def get_attributes(self) -> list[str]:
"""Return a list of attributes name"""
@@ -31,6 +35,7 @@ class MClient(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
@@ -50,6 +55,7 @@ class MUser(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
@@ -70,12 +76,14 @@ class MAdmin(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
score_connexion: int = 0
geoip: str = None
connexion_datetime: datetime = field(default=datetime.now())
language: str = "EN"
level: int = 0
@dataclass
@@ -89,6 +97,7 @@ class MReputation(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
@@ -200,6 +209,9 @@ class MConfig(MainModel):
PASSWORD: str = "password"
"""The password of the admin of the service"""
RPC_USERS: list[dict] = field(default_factory=list)
"""The Defender rpc users"""
JSONRPC_URL: str = None
"""The RPC url, if local https://127.0.0.1:PORT/api should be fine"""
@@ -344,6 +356,14 @@ class MModule(MainModel):
class_name: str = None
class_instance: Optional[Any] = None
@dataclass
class DefenderModuleHeader(MainModel):
name: str = ''
version: str = ''
description: str = ''
author: str = ''
core_version: str = ''
@dataclass
class MSModule:
"""Server Modules model"""
@@ -362,5 +382,16 @@ class MSasl(MainModel):
username: Optional[str] = None
password: Optional[str] = None
fingerprint: Optional[str] = None
language: str = "EN"
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

@@ -6,13 +6,13 @@ import time
from ssl import SSLSocket
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, Optional, Union
from core.classes import rehash
from core.loader import Loader
from core.classes.protocol import Protocol
from core.classes.modules import rehash
from core.classes.interfaces.iprotocol import IProtocol
from core.utils import tr
if TYPE_CHECKING:
from core.definition import MSasl
from core.loader import Loader
class Irc:
_instance = None
@@ -24,7 +24,7 @@ class Irc:
return cls._instance
def __init__(self, loader: Loader) -> 'Irc':
def __init__(self, loader: 'Loader'):
# Loader class
self.Loader = loader
@@ -125,17 +125,19 @@ class Irc:
self.build_command(3, 'core', 'cert', 'Append your new fingerprint to your account!')
self.build_command(4, 'core', 'rehash', 'Reload the configuration file without restarting')
self.build_command(4, 'core', 'raw', 'Send a raw command directly to the IRC server')
self.build_command(4, 'core', 'print_vars', 'Print users in a file.')
self.build_command(4, 'core', 'start_rpc', 'Start defender jsonrpc server')
self.build_command(4, 'core', 'stop_rpc', 'Stop defender jsonrpc server')
# Define the IrcSocket object
self.IrcSocket: Optional[Union[socket.socket, SSLSocket]] = None
self.__create_table()
self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, ))
##############################################
# CONNEXION IRC #
##############################################
def init_irc(self, ircInstance: 'Irc') -> None:
def init_irc(self) -> None:
"""Create a socket and connect to irc server
Args:
@@ -143,8 +145,8 @@ class Irc:
"""
try:
self.init_service_user()
self.Utils.create_socket(ircInstance)
self.__connect_to_irc(ircInstance)
self.Utils.create_socket(self)
self.__connect_to_irc()
except AssertionError as ae:
self.Logs.critical(f'Assertion error: {ae}')
@@ -161,35 +163,39 @@ class Irc:
))
return None
def __connect_to_irc(self, ircInstance: 'Irc') -> None:
def __connect_to_irc(self) -> None:
try:
self.init_service_user()
self.ircObject = ircInstance # créer une copie de l'instance Irc
self.Protocol = Protocol(
protocol=self.Config.SERVEUR_PROTOCOL,
ircInstance=self.ircObject
).Protocol
self.Protocol: 'IProtocol' = self.Loader.PFactory.get()
self.Protocol.register_command()
self.Protocol.send_link() # Etablir le link en fonction du protocol choisi
self.signal = True # Une variable pour initier la boucle infinie
self.join_saved_channels() # Join existing channels
self.ModuleUtils.db_load_all_existing_modules(self)
# self.join_saved_channels() # Join existing channels
# self.ModuleUtils.db_load_all_existing_modules(self)
while self.signal:
try:
if self.Config.DEFENDER_RESTART == 1:
rehash.restart_service(self.ircObject)
rehash.restart_service(self)
# 4072 max what the socket can grab
buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
data_in_bytes = self.IrcSocket.recv(buffer_size)
data = data_in_bytes.splitlines(True)
count_bytes = len(data_in_bytes)
while count_bytes > 4070:
# If the received message is > 4070 then loop and add the value to the variable
eol = True
if data_in_bytes[-2:] != b"\r\n":
eol = False
while not eol:
new_data = self.IrcSocket.recv(buffer_size)
data_in_bytes += new_data
count_bytes = len(new_data)
if data_in_bytes[-2:] == eol:
eol = False
# while count_bytes > 4070:
# # If the received message is > 4070 then loop and add the value to the variable
# new_data = self.IrcSocket.recv(buffer_size)
# data_in_bytes += new_data
# count_bytes = len(new_data)
data = data_in_bytes.splitlines(True)
@@ -202,9 +208,11 @@ class Irc:
self.Logs.error(f"SSLEOFError __connect_to_irc: {soe} - {data}")
except ssl.SSLError as se:
self.Logs.error(f"SSLError __connect_to_irc: {se} - {data}")
sys.exit(1)
sys.exit(-1)
except OSError as oe:
self.Logs.error(f"SSLError __connect_to_irc: {oe} - {data}")
self.Logs.error(f"SSLError __connect_to_irc: {oe} {oe.errno}")
if oe.errno == 10053:
sys.exit(-1)
except (socket.error, ConnectionResetError):
self.Logs.debug("Connexion reset")
@@ -220,7 +228,7 @@ class Irc:
except ssl.SSLEOFError as soe:
self.Logs.error(f"SSLEOFError: {soe}")
except AttributeError as atte:
self.Logs.critical(f"AttributeError: {atte}")
self.Logs.critical(f"AttributeError: {atte}", exc_info=True)
except Exception as e:
self.Logs.critical(f"General Error: {e}", exc_info=True)
@@ -261,9 +269,9 @@ class Irc:
# This is only to reference the method
return None
##############################################
# --------------------------------------------
# FIN CONNEXION IRC #
##############################################
# --------------------------------------------
def build_command(self, level: int, module_name: str, command_name: str, command_description: str) -> None:
"""This method build the commands variable
@@ -313,15 +321,15 @@ class Irc:
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 FROM {self.Config.TABLE_ADMIN} WHERE 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 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)
user_from_db = result.fetchone()
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:
return None
@@ -331,6 +339,7 @@ class Irc:
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.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")
else:
@@ -345,6 +354,7 @@ class Irc:
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.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")
else:
@@ -352,11 +362,6 @@ class Irc:
self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} SASL {self.Settings.MAIN_SERVER_HOSTNAME} {s.client_uid} D F")
self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} 904 {s.username} :SASL authentication failed")
def __create_table(self) -> None:
"""## Create core tables
"""
pass
def get_defender_uptime(self) -> str:
"""Savoir depuis quand Defender est connecté
@@ -369,7 +374,7 @@ class Irc:
return uptime
def heartbeat(self, beat:float) -> None:
def heartbeat(self, beat: float) -> None:
"""Execute certaines commandes de nettoyage toutes les x secondes
x étant définit a l'initialisation de cette class (self.beat)
@@ -380,14 +385,16 @@ class Irc:
time.sleep(beat)
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)
if user_obj is None:
return None
self.Admin.insert(
self.Loader.Definition.MAdmin(
**user_obj.to_dict(),
language=language,
account=account,
level=int(level)
)
@@ -405,57 +412,58 @@ class Irc:
return None
def create_defender_user(self, nickname: str, level: int, password: str) -> str:
def create_defender_user(self, sender: str, new_admin: str, new_level: int, new_password: str) -> bool:
"""Create a new admin user for defender
Args:
sender (str): The current admin sending the request
new_admin (str): The new admin to create
new_level (int): The level of the admin
new_password (str): The clear password
Returns:
bool: True if created.
"""
# > addaccess [nickname] [level] [password]
dnick = self.Config.SERVICE_NICKNAME
p = self.Protocol
get_user = self.User.get_user(nickname)
level = self.Base.convert_to_int(level)
password = password
get_user = self.User.get_user(new_admin)
level = self.Base.convert_to_int(new_level)
password = new_password
if get_user is None:
response = f'This nickname {nickname} does not exist, it is not possible to create this user'
self.Logs.warning(response)
return response
response = tr("The nickname (%s) is not currently connected! please create a new admin when the nickname is connected to the network!", new_admin)
p.send_notice(dnick, sender, response)
self.Logs.debug(f"New admin {new_admin} sent by {sender} is not connected")
return False
if level is None:
response = f'The level [{level}] must be a number from 1 to 4'
self.Logs.warning(response)
return response
if level > 4:
response = "Impossible d'ajouter un niveau > 4"
self.Logs.warning(response)
return response
if level is None or level > 4 or level == 0:
p.send_notice(dnick, sender, tr("The level (%s) must be a number from 1 to 4", level))
self.Logs.debug(f"Level must a number between 1 to 4 (sent by {sender})")
return False
nickname = get_user.nickname
response = ''
hostname = get_user.hostname
vhost = get_user.vhost
spassword = self.Loader.Utils.hash_password(password)
mes_donnees = {'admin': nickname}
query_search_user = f"SELECT id FROM {self.Config.TABLE_ADMIN} WHERE user=:admin"
r = self.Base.db_execute_query(query_search_user, mes_donnees)
exist_user = r.fetchone()
# On verifie si le user exist dans la base
if not exist_user:
mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level}
# Check if the user already exist
if not self.Admin.db_is_admin_exist(nickname):
mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level, 'language': self.Config.LANG}
self.Base.db_execute_query(f'''INSERT INTO {self.Config.TABLE_ADMIN}
(createdOn, user, password, hostname, vhost, level) VALUES
(:datetime, :user, :password, :hostname, :vhost, :level)
(createdOn, user, password, hostname, vhost, level, language) VALUES
(:datetime, :user, :password, :hostname, :vhost, :level, :language)
''', mes_donnees)
response = f"{nickname} ajouté en tant qu'administrateur de niveau {level}"
self.Protocol.send_notice(nick_from=self.Config.SERVICE_NICKNAME, nick_to=nickname, msg=response)
self.Logs.info(response)
return response
p.send_notice(dnick, sender, tr("New admin (%s) has been added with level %s", nickname, level))
self.Logs.info(f"A new admin ({nickname}) has been created by {sender}!")
return True
else:
response = f'{nickname} Existe déjà dans les users enregistrés'
self.Protocol.send_notice(nick_from=self.Config.SERVICE_NICKNAME, nick_to=nickname, msg=response)
self.Logs.info(response)
return response
p.send_notice(dnick, sender, tr("The nickname (%s) Already exist!", nickname))
self.Logs.info(f"The nickname {nickname} already exist! (sent by {sender})")
return False
def thread_check_for_new_version(self, fromuser: str) -> None:
dnickname = self.Config.SERVICE_NICKNAME
@@ -476,133 +484,26 @@ class Irc:
"""
try:
original_response: list[str] = data.copy()
RED = self.Config.COLORS.red
GREEN = self.Config.COLORS.green
NOGC = self.Config.COLORS.nogc
if len(original_response) < 2:
self.Logs.warning(f'Size ({str(len(original_response))}) - {original_response}')
return None
self.Logs.debug(f">> {self.Utils.hide_sensitive_data(original_response)}")
parsed_protocol = self.Protocol.parse_server_msg(original_response.copy())
match parsed_protocol:
pos, parsed_protocol = self.Protocol.get_ircd_protocol_poisition(cmd=original_response, log=True)
modules = self.ModuleUtils.model_get_loaded_modules().copy()
case 'PING':
self.Protocol.on_server_ping(serverMsg=original_response)
case 'SERVER':
self.Protocol.on_server(serverMsg=original_response)
case 'SJOIN':
self.Protocol.on_sjoin(serverMsg=original_response)
case 'EOS':
self.Protocol.on_eos(serverMsg=original_response)
case 'UID':
try:
self.Protocol.on_uid(serverMsg=original_response)
for module in self.ModuleUtils.model_get_loaded_modules().copy():
module.class_instance.cmd(original_response)
# SASL authentication
# ['@s2s-md/..', ':001', 'UID', 'adator__', '0', '1755987444', '...', 'desktop-h1qck20.mshome.net', '001XLTT0U', '0', '+iwxz', '*', 'Clk-EC2256B2.mshome.net', 'rBKAAQ==', ':...']
dnickname = self.Config.SERVICE_NICKNAME
dchanlog = self.Config.SERVICE_CHANLOG
uid = original_response[8]
nickname = original_response[3]
sasl_obj = self.Sasl.get_sasl_obj(uid)
if sasl_obj:
if sasl_obj.auth_success:
self.insert_db_admin(sasl_obj.client_uid, sasl_obj.username, sasl_obj.level)
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 original_response[2] != 'UID':
# Envoyer la commande aux classes dynamiquement chargées
for module in self.ModuleUtils.model_get_loaded_modules().copy():
for parsed in self.Protocol.Handler.get_ircd_commands():
if parsed.command_name.upper() == parsed_protocol:
parsed.func(original_response)
for module in modules:
module.class_instance.cmd(original_response)
# if len(original_response) > 2:
# if original_response[2] != 'UID':
# # Envoyer la commande aux classes dynamiquement chargées
# for module in self.ModuleUtils.model_get_loaded_modules().copy():
# module.class_instance.cmd(original_response)
except IndexError as ie:
self.Logs.error(f"IndexError: {ie}")
except Exception as err:
@@ -620,12 +521,21 @@ class Irc:
Returns:
None: Nothing to return
"""
u = self.User.get_user(user)
"""The User Object"""
if u is None:
return None
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
c = self.Client.get_Client(u.uid)
"""The Client Object"""
fromuser = u.nickname
uid = u.uid
self.Settings.current_admin = self.Admin.get_admin(user) # set Current admin if any.
RED = self.Config.COLORS.red
GREEN = self.Config.COLORS.green
BLACK = self.Config.COLORS.black
NOGC = self.Config.COLORS.nogc
# Defender information
@@ -668,7 +578,7 @@ class Irc:
case 'deauth':
current_command = str(cmd[0]).upper()
uid_to_deauth = self.User.get_uid(fromuser)
uid_to_deauth = uid
self.delete_db_admin(uid_to_deauth)
self.Protocol.send_priv_msg(
@@ -677,12 +587,24 @@ class Irc:
channel=dchanlog
)
self.Protocol.send_notice(dnickname, fromuser, tr("You have been successfully disconnected from %s", dnickname))
return None
case 'firstauth':
# firstauth OWNER_NICKNAME OWNER_PASSWORD
current_nickname = self.User.get_nickname(fromuser)
current_uid = self.User.get_uid(fromuser)
# Syntax. /msg defender firstauth OWNER_NICKNAME OWNER_PASSWORD
# Check command
current_nickname = fromuser
current_uid = uid
current_command = str(cmd[0])
if current_nickname is None:
self.Logs.critical(f"This nickname [{fromuser}] don't exist")
return None
if len(cmd) < 3:
self.Protocol.send_notice(dnickname,fromuser, tr("Syntax. /msg %s %s [OWNER_NICKNAME] [OWNER_PASSWORD]", self.Config.SERVICE_NICKNAME, current_command))
return None
query = f"SELECT count(id) as c FROM {self.Config.TABLE_ADMIN}"
result = self.Base.db_execute_query(query)
result_db = result.fetchone()
@@ -693,11 +615,7 @@ class Irc:
nick_to=fromuser,
msg=tr("You can't use this command anymore ! Please use [%sauth] instead", self.Config.SERVICE_PREFIX)
)
return False
if current_nickname is None:
self.Logs.critical(f"This nickname [{fromuser}] don't exist")
return False
return None
# Credentials sent from the user
cmd_owner = str(cmd[1])
@@ -707,59 +625,33 @@ class Irc:
config_owner = self.Config.OWNER
config_password = self.Config.PASSWORD
if current_nickname != cmd_owner:
self.Logs.critical(f"The current nickname [{fromuser}] is different than the nickname sent [{cmd_owner}] !")
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"The current nickname [{fromuser}] is different than the nickname sent [{cmd_owner}] !"
)
return False
if current_nickname != config_owner:
self.Logs.critical(f"The current nickname [{current_nickname}] is different than the configuration owner [{config_owner}] !")
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"The current nickname [{current_nickname}] is different than the configuration owner [{config_owner}] !"
)
return False
if cmd_owner != config_owner:
self.Logs.critical(f"The nickname sent [{cmd_owner}] is different than the configuration owner [{config_owner}] !")
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"The nickname sent [{cmd_owner}] is different than the configuration owner [{config_owner}] !"
msg=tr("The nickname sent [%s] is different than the one set in the configuration file !", cmd_owner)
)
return False
return None
if cmd_owner == config_owner and cmd_password == config_password:
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(
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=tr("[%s %s %s] - %s is now connected to %s", GREEN, current_command.upper(), NOGC, fromuser, dnickname),
nick_from=dnickname,
channel=dchanlog
)
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"Connexion a {dnickname} réussie!"
)
self.Protocol.send_notice(dnickname, fromuser, tr("Successfuly connected to %s", dnickname))
else:
self.Protocol.send_priv_msg(
msg=f"[ {self.Config.COLORS.red}{str(current_command).upper()} ]{self.Config.COLORS.black} - {self.User.get_nickname(fromuser)} a tapé un mauvais mot de pass",
msg=tr("[ %s %s %s ] - %s provided a wrong password!", RED, current_command.upper(), NOGC, current_nickname),
nick_from=dnickname,
channel=dchanlog
)
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"Mot de passe incorrecte"
)
self.Protocol.send_notice(dnickname, fromuser, tr("Wrong password!"))
case 'auth':
# Syntax. !auth nickname password
@@ -767,44 +659,45 @@ class Irc:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} [nickname] [password]")
return None
current_command = cmd[0]
user_to_log = cmd[1]
password = cmd[2]
current_client = self.User.get_user(fromuser)
current_client = u
admin_obj = self.Admin.get_admin(fromuser)
if current_client is None:
# This case should never happen
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {GREEN}{str(current_command).upper()}{NOGC} ] - Nickname {fromuser} is trying to connect to defender wrongly",
msg=f"[ {RED}{str(command).upper()} FAIL{NOGC} ] - Nickname {fromuser} is trying to connect to defender wrongly",
channel=dchanlog)
return None
if admin_obj:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {GREEN}{str(current_command).upper()}{NOGC} ] - You are already connected to {dnickname}",
msg=f"[ {GREEN}{str(command).upper()}{NOGC} ] - {fromuser} is already connected to {dnickname}",
channel=dchanlog)
self.Protocol.send_notice(dnickname, fromuser, tr("You are already connected to %s", dnickname))
return None
mes_donnees = {'user': user_to_log, 'password': self.Loader.Utils.hash_password(password)}
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)
user_from_db = result.fetchone()
if user_from_db:
account = user_from_db[1]
level = user_from_db[2]
self.insert_db_admin(current_client.uid, account, level)
account = str(user_from_db[1])
level = int(user_from_db[2])
language = str(user_from_db[3])
self.insert_db_admin(current_client.uid, account, level, language)
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(command).upper()} SUCCESS{NOGC} ] - {current_client.nickname} ({account}) est désormais connecté a {dnickname}",
channel=dchanlog)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Connexion a {dnickname} réussie!")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=tr("Successfuly connected to %s", dnickname))
return None
else:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {RED}{str(current_command).upper()}{NOGC} ] - {current_client.nickname} a tapé un mauvais mot de pass",
msg=f"[ {RED}{str(command).upper()} FAIL{NOGC} ] - {current_client.nickname} a tapé un mauvais mot de pass",
channel=dchanlog)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Mot de passe incorrecte")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=tr("Wrong password!"))
return None
case 'addaccess':
@@ -813,15 +706,14 @@ class Irc:
if len(cmd) < 4:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Right command : /msg {dnickname} addaccess [nickname] [level] [password]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"level: from 1 to 4")
return None
newnickname = cmd[1]
newlevel = self.Base.int_if_possible(cmd[2])
password = cmd[3]
new_admin = str(cmd[1])
level = self.Base.int_if_possible(cmd[2])
password = str(cmd[3])
response = self.create_defender_user(newnickname, newlevel, password)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{response}")
self.Logs.info(response)
self.create_defender_user(fromuser, new_admin, level, password)
return None
except IndexError as ie:
self.Logs.error(f'_hcmd addaccess: {ie}')
@@ -831,7 +723,7 @@ class Irc:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} addaccess [nickname] [level] [password]")
case 'editaccess':
# .editaccess [USER] [PASSWORD] [LEVEL]
# .editaccess [USER] [NEW_PASSWORD] [LEVEL]
try:
if len(cmd) < 3:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Right command : /msg {dnickname} editaccess [nickname] [NEWPASSWORD] [NEWLEVEL]")
@@ -846,8 +738,8 @@ class Irc:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {fromuser} has no Admin access")
return None
current_user = self.User.get_nickname(fromuser)
current_uid = self.User.get_uid(fromuser)
current_user = fromuser
current_uid = uid
current_user_level = get_admin.level
user_new_level = int(cmd[3]) if len(cmd) == 4 else get_admin.level
@@ -911,8 +803,8 @@ class Irc:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {fromuser} has no admin access")
return None
current_user = self.User.get_nickname(fromuser)
current_uid = self.User.get_uid(fromuser)
current_user = fromuser
current_uid = uid
current_user_level = get_admin.level
# Rechercher le user dans la base de données.
@@ -941,14 +833,37 @@ class Irc:
case 'cert':
# Syntax !cert
try:
if len(cmd) < 2:
self.Protocol.send_notice(dnickname, fromuser, f"Right command : /msg {dnickname} cert add")
self.Protocol.send_notice(dnickname, fromuser, f"Right command : /msg {dnickname} cert del")
return None
admin_obj = self.Admin.get_admin(fromuser)
if admin_obj:
query = f'UPDATE {self.Config.TABLE_ADMIN} SET fingerprint = :fingerprint WHERE user = :user'
r = self.Base.db_execute_query(query, {'fingerprint': admin_obj.fingerprint, 'user': admin_obj.account})
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}')
param = cmd[1] # add or del
match param:
case 'add':
if admin_obj:
if admin_obj.fingerprint is not None:
query = f'UPDATE {self.Config.TABLE_ADMIN} SET fingerprint = :fingerprint WHERE user = :user'
r = self.Base.db_execute_query(query, {'fingerprint': admin_obj.fingerprint, 'user': admin_obj.account})
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:
self.Protocol.send_notice(dnickname, fromuser, f'[ {RED}CERT{NOGC} ] There is no fingerprint to add.')
case 'del':
if admin_obj:
query = f"UPDATE {self.Config.TABLE_ADMIN} SET fingerprint = :fingerprint WHERE user =:user"
r = self.Base.db_execute_query(query, {'fingerprint': None, 'user': admin_obj.account})
if r.rowcount > 0:
self.Protocol.send_notice(dnickname, fromuser, f'[ {GREEN}CERT{NOGC} ] Your fingerprint has been removed from your account. {admin_obj.fingerprint}')
else:
self.Protocol.send_notice(dnickname, fromuser, f'[ {RED}CERT{NOGC} ] Impossible to remove your fingerprint.{admin_obj.fingerprint}')
case _:
self.Protocol.send_notice(dnickname, fromuser, f"Right command : /msg {dnickname} cert add")
self.Protocol.send_notice(dnickname, fromuser, f"Right command : /msg {dnickname} cert del")
return None
except Exception as e:
self.Logs.error(e)
@@ -976,7 +891,7 @@ class Irc:
)
return None
user_obj = self.User.get_user(fromuser)
user_obj = u
if user_obj is None:
self.Logs.error(f"Nickname ({fromuser}) doesn't exist, it is impossible to register this nickname")
@@ -1031,8 +946,8 @@ class Irc:
account = str(cmd[1]) # account
encrypted_password = self.Loader.Utils.hash_password(cmd[2])
user_obj = self.User.get_user(fromuser)
client_obj = self.Client.get_Client(user_obj.uid)
user_obj = u
client_obj = c
if client_obj is not None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You are already logged in")
@@ -1072,19 +987,18 @@ class Irc:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} <account>")
return None
user_obj = self.User.get_user(fromuser)
user_obj = u
if user_obj is None:
self.Logs.error(f"The User [{fromuser}] is not available in the database")
return None
client_obj = self.Client.get_Client(user_obj.uid)
client_obj = c
if client_obj is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Nothing to logout. please login first")
return None
self.Protocol.send_svslogout(client_obj)
# self.Protocol.send_svsmode(nickname=fromuser, user_mode='-r')
self.Client.delete(user_obj.uid)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You have been logged out successfully")
@@ -1104,18 +1018,25 @@ class Irc:
case 'load':
try:
# Load a module ex: .load mod_defender
if len(cmd) < 2:
self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper()))
return None
mod_name = str(cmd[1])
self.ModuleUtils.load_one_module(self, mod_name, fromuser)
return None
except KeyError as ke:
self.Logs.error(f"Key Error: {ke} - list recieved: {cmd}")
except Exception as err:
self.Logs.error(f"General Error: {ke} - list recieved: {cmd}")
self.Logs.error(f"General Error: {err} - list recieved: {cmd}", exc_info=True)
case 'unload':
# unload mod_defender
try:
# The module name. exemple: mod_defender
if len(cmd) < 2:
self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper()))
return None
module_name = str(cmd[1]).lower()
self.ModuleUtils.unload_one_module(self, module_name, False)
return None
@@ -1126,6 +1047,10 @@ class Irc:
# reload mod_defender
try:
# ==> mod_defender
if len(cmd) < 2:
self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper()))
return None
module_name = str(cmd[1]).lower()
self.ModuleUtils.reload_one_module(self, module_name, fromuser)
return None
@@ -1146,7 +1071,8 @@ class Irc:
self.Base.execute_periodic_action()
for chan_name in self.Channel.UID_CHANNEL_DB:
self.Protocol.send_mode_chan(chan_name.name, '-l')
# self.Protocol.send_mode_chan(chan_name.name, '-l')
self.Protocol.send_set_mode('-l', channel_name=chan_name.name)
for client in self.Client.CLIENT_DB:
self.Protocol.send_svslogout(client)
@@ -1167,11 +1093,15 @@ class Irc:
case 'restart':
final_reason = ' '.join(cmd[1:])
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{dnickname.capitalize()} is going to restart!")
self.Config.DEFENDER_RESTART = 1 # Set restart status to 1 saying that the service will restart
self.Config.DEFENDER_INIT = 1 # set init to 1 saying that the service will be re initiated
# Set restart status to 1 saying that the service will restart
self.Config.DEFENDER_RESTART = 1
# set init to 1 saying that the service will be re initiated
self.Config.DEFENDER_INIT = 1
case 'rehash':
rehash.rehash_service(self.ircObject, fromuser)
rehash.rehash_service(self, fromuser)
return None
case 'show_modules':
@@ -1192,14 +1122,14 @@ class Irc:
self.Protocol.send_notice(
nick_from=dnickname,
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
else:
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"{module} - {RED}Not Loaded{NOGC}"
msg=tr('%s - %sNot Loaded%s', module, RED, NOGC)
)
case 'show_timers':
@@ -1269,7 +1199,7 @@ class Irc:
self.Protocol.send_notice(
nick_from=dnickname,
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
@@ -1304,7 +1234,7 @@ class Irc:
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"{uptime}"
msg=uptime
)
return None
@@ -1312,7 +1242,7 @@ class Irc:
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f">> Defender V.{self.Config.CURRENT_VERSION} Developped by adator®."
msg=f">> Defender V{self.Config.CURRENT_VERSION} Developped by adator®."
)
return None
@@ -1325,5 +1255,30 @@ class Irc:
self.Protocol.send_raw(raw_command)
return None
case 'print_vars':
with open('users.txt', 'w') as fw:
i = 1
for u in self.User.UID_DB:
w = fw.write(u.to_dict().__str__() + "\n")
self.Logs.debug(f" {i} - chars written {w}")
i += 1
self.Protocol.send_priv_msg(dnickname, "Data written in users.txt file", dchanlog)
with open('modules.txt', 'w') as fw:
i = 1
for u in self.ModuleUtils.DB_MODULE_HEADERS:
w = fw.write(u.to_dict().__str__() + "\n")
self.Logs.debug(f" {i} - chars written {w}")
i += 1
self.Protocol.send_priv_msg(dnickname, "Data written in modules.txt file", dchanlog)
return None
case 'start_rpc':
self.Loader.RpcServer.start_server()
case 'stop_rpc':
self.Loader.RpcServer.stop_server()
case _:
pass

View File

@@ -2,3 +2,20 @@ traduction:
# Message help
- orig: "Access denied!"
trad: "Accès refusé."
- orig: "Wrong password!"
trad: "Mot de passe incorrect!"
- orig: "%s - %sLoaded%s by %s on %s"
trad: "%s - %sChargé%s par %s le %s"
- orig: "Module %s loaded!"
trad: "Module %s chargé!"
- orig: "cmd method is not available in the module (%s)"
trad: "La méthode cmd n'est pas disponible dans le module (%s)"
- orig: "[%sMODULE ERROR%s] Module %s is facing issues! %s"
trad: "[%sMODULE ERREUR%s] Le module %s a rencontré une erreur! %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

@@ -1,13 +1,16 @@
from logging import Logger
from core.classes.settings import global_settings
from core.classes import translation, user, admin, client, channel, reputation, settings, sasl
from core.classes.modules.settings import global_settings
from core.classes.modules import translation, user, admin, client, channel, reputation, settings, sasl
import core.logs as logs
import core.definition as df
import core.utils as utils
import core.base as base_mod
import core.module as module_mod
import core.classes.commands as commands_mod
import core.classes.config as conf_mod
import core.classes.modules.commands as commands_mod
import core.classes.modules.config as conf_mod
import core.classes.modules.rpc.rpc as rpc_mod
import core.irc as irc
import core.classes.protocols.factory as factory
class Loader:
@@ -24,6 +27,8 @@ class Loader:
self.LoggingModule: logs = logs
self.RpcServerModule: rpc_mod = rpc_mod
self.Utils: utils = utils
# Load Classes
@@ -33,7 +38,7 @@ class Loader:
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"
@@ -63,4 +68,12 @@ class Loader:
self.Sasl: sasl.Sasl = sasl.Sasl(self)
self.Irc: irc.Irc = irc.Irc(self)
self.PFactory: factory.ProtocolFactorty = factory.ProtocolFactorty(self.Irc)
self.RpcServer: rpc_mod.JSONRPCServer = rpc_mod.JSONRPCServer(self)
self.Base.init()
self.Logs.debug(self.Utils.tr("Loader %s success", __name__))

View File

@@ -15,7 +15,7 @@ class ServiceLogging:
self.SERVER_PREFIX = None
self.LOGGING_CONSOLE = True
self.LOG_FILTERS: list[str] = ['PING', f":{self.SERVER_PREFIX}auth", "['PASS'"]
self.LOG_FILTERS: list[str] = ["PING", f":{self.SERVER_PREFIX}auth", "['PASS'"]
self.file_handler = None
self.stdout_handler = None

View File

@@ -6,7 +6,8 @@ import sys
import importlib
from types import ModuleType
from typing import TYPE_CHECKING, Optional
from core.definition import MModule
from core.definition import DefenderModuleHeader, MModule
from core.utils import tr
if TYPE_CHECKING:
from core.loader import Loader
@@ -15,6 +16,7 @@ if TYPE_CHECKING:
class Module:
DB_MODULES: list[MModule] = []
DB_MODULE_HEADERS: list[DefenderModuleHeader] = []
def __init__(self, loader: 'Loader') -> None:
self.__Loader = loader
@@ -31,18 +33,57 @@ class Module:
list[str]: List of all module names.
"""
base_path = Path('mods')
return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')]
modules_available = [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')]
self.__Logs.debug(f"Modules available: {modules_available}")
return modules_available
def get_module_information(self, module_name: str) -> tuple[str, str, str]:
def get_module_information(self, module_name: str) -> tuple[Optional[str], Optional[str], Optional[str]]:
# module_name : mod_defender
if not module_name.lower().startswith('mod_'):
return None, None, None
module_name = module_name.lower()
module_name = module_name.lower() # --> mod_defender
module_folder = module_name.split('_')[1].lower() # --> defender
class_name = module_name.split('_')[1].capitalize() # --> Defender
self.__Logs.debug(f"Module information Folder: {module_folder}, Name: {module_name}, Class: {class_name}")
return module_folder, module_name, class_name
def get_module_header(self, module_name: str) -> Optional[DefenderModuleHeader]:
for mod_h in self.DB_MODULE_HEADERS:
if module_name.lower() == mod_h.name.lower():
self.__Logs.debug(f"Module Header found: {mod_h}")
return mod_h
return None
def create_module_header(self, module_header: dict[str, str]) -> bool:
"""Create a new module header into DB_MODULE_HEADERS
Args:
module_header (dict[str, str]): The module header
Returns:
bool: True if the module header has been created.
"""
mod_header = DefenderModuleHeader(**module_header)
if self.get_module_header(mod_header.name) is None:
self.__Logs.debug(f"[MOD_HEADER] The module header has been created! ({mod_header.name} v{mod_header.version})")
self.DB_MODULE_HEADERS.append(mod_header)
return True
return False
def delete_module_header(self, module_name: str) -> bool:
mod_header = self.get_module_header(module_name)
if mod_header is not None:
self.__Logs.debug(f"[MOD_HEADER] The module header has been deleted ({mod_header.name} v{mod_header.version})")
self.DB_MODULE_HEADERS.remove(mod_header)
return True
self.__Logs.debug(f"[MOD_HEADER ERROR] Impossible to remove the module header ({module_name})")
return False
def load_one_module(self, uplink: 'Irc', module_name: str, nickname: str, is_default: bool = False) -> bool:
module_folder, module_name, class_name = self.get_module_information(module_name)
@@ -66,14 +107,26 @@ class Module:
return self.reload_one_module(uplink, module_name, nickname)
# Charger le module
loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}')
my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe
create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe
try:
loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}')
my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe
create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe
self.create_module_header(create_instance_of_the_class.MOD_HEADER)
except AttributeError as attr:
red = uplink.Config.COLORS.red
nogc = uplink.Config.COLORS.nogc
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=tr("[%sMODULE ERROR%s] Module %s is facing issues! %s", red, nogc, module_name, attr),
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.error(msg=attr, exc_info=True)
return False
if not hasattr(create_instance_of_the_class, 'cmd'):
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module {module_name} ne contient pas de méthode cmd",
msg=tr("cmd method is not available in the module (%s)", module_name),
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available")
@@ -86,11 +139,14 @@ class Module:
self.db_register_module(module_name, nickname, is_default)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module {module_name} chargé",
msg=tr("Module %s loaded!", module_name),
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module {class_name} has been loaded")
return True
return False
def load_all_modules(self) -> bool:
...
@@ -113,6 +169,7 @@ class Module:
if self.is_module_exist_in_sys_module(module_name):
module_model = self.model_get_module(module_name)
if module_model:
self.delete_module_header(module_model.class_instance.MOD_HEADER['name'])
module_model.class_instance.unload()
else:
uplink.Protocol.send_priv_msg(
@@ -130,6 +187,7 @@ class Module:
importlib.reload(the_module)
my_class = getattr(the_module, class_name, None)
new_instance = my_class(uplink)
self.create_module_header(new_instance.MOD_HEADER)
module_model.class_instance = new_instance
# Créer le module dans la base de données
@@ -152,7 +210,7 @@ class Module:
return False
except (TypeError, AttributeError, KeyError, Exception) as err:
self.__Logs.error(f"[RELOAD MODULE ERROR]: {err}")
self.__Logs.error(f"[RELOAD MODULE ERROR]: {err}", exc_info=True)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[RELOAD MODULE ERROR]: {err}",
@@ -193,7 +251,9 @@ class Module:
"""Unload a module
Args:
mod_name (str): Module name ex mod_defender
uplink (Irc): The Irc instance
module_name (str): Module name ex mod_defender
keep_in_db (bool): Keep in database
Returns:
bool: True if success
@@ -206,6 +266,7 @@ class Module:
module = self.model_get_module(module_name)
if module is None:
self.__Logs.debug(f"[ UNLOAD MODULE ERROR ] This module {module_name} is not loaded!")
self.db_delete_module(module_name)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}UNLOAD MODULE ERROR{nogc} ] This module {module_name} is not loaded!",
@@ -214,6 +275,7 @@ class Module:
return False
if module:
self.delete_module_header(module.class_instance.MOD_HEADER['name'])
module.class_instance.unload()
self.DB_MODULES.remove(module)
@@ -240,7 +302,7 @@ class Module:
return False
except Exception as err:
self.__Logs.error(f"General Error: {err}")
self.__Logs.error(f"General Error: {err}", exc_info=True)
return False
def unload_all_modules(self) -> bool:
@@ -257,7 +319,9 @@ class Module:
"""
module_folder, module_name, class_name = self.get_module_information(module_name)
if "mods." + module_folder + "." + module_name in sys.modules:
self.__Logs.debug(f"[SYS MODULE] (mods.{module_folder}.{module_name}) found in sys.modules")
return True
self.__Logs.debug(f"[SYS MODULE] (mods.{module_folder}.{module_name}) not found in sys.modules")
return False
'''

View File

@@ -1,20 +1,18 @@
'''
"""
Main utils library.
'''
"""
import gc
import glob
import ssl
import socket
import sys
from pathlib import Path
from re import match, sub
from base64 import b64decode
from typing import Literal, Optional, Any, TYPE_CHECKING
from datetime import datetime, timedelta, timezone
from datetime import datetime
from time import time
from random import choice
from hashlib import md5, sha3_512
from core.classes.settings import global_settings
from core.classes.modules.settings import global_settings
if TYPE_CHECKING:
from core.irc import Irc
@@ -37,28 +35,27 @@ def tr(message: str, *args) -> str:
is_args_available = True if args else False
g = global_settings
try:
# Access to user object ==> global_instance.get_user_option
client_language = global_settings.global_user.current_user.geoip if global_settings.global_user.current_user else 'en'
client_language = client_language if client_language else 'en'
# Access to admin object
client_language = g.current_admin.language if g.current_admin else g.global_lang
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
if g.global_lang is None:
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
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]):
return trads[1] % args if is_args_available else trads[1]
return message % args if is_args_available else message
except KeyError as ke:
g.global_logger.error(f"Key Error: {ke}")
g.global_logger.error(f"KeyError: {ke}")
return message % args if is_args_available else message
except Exception as err:
@@ -86,9 +83,9 @@ def get_unixtime() -> int:
Returns:
int: Current time in seconds since the Epoch (int)
"""
cet_offset = timezone(timedelta(hours=2))
now_cet = datetime.now(cet_offset)
unixtime_cet = int(now_cet.timestamp())
# cet_offset = timezone(timedelta(hours=2))
# now_cet = datetime.now(cet_offset)
# unixtime_cet = int(now_cet.timestamp())
return int(time())
def get_sdatetime() -> str:
@@ -144,7 +141,9 @@ def create_socket(uplink: 'Irc') -> None:
except OSError as oe:
uplink.Logs.critical(f"[OS Error]: {oe}")
if 'connection refused' in str(oe).lower():
sys.exit(oe)
sys.exit(oe.__str__())
if oe.errno == 10053:
sys.exit(oe.__str__())
except AttributeError as ae:
uplink.Logs.critical(f"AttributeError: {ae}")
@@ -178,7 +177,7 @@ def generate_random_string(lenght: int) -> str:
return randomize
def hash_password(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str:
def hash_password(password: str, algorithm: Literal["md5", "sha3_512"] = 'md5') -> str:
"""Return the crypted password following the selected algorithm
Args:
@@ -191,16 +190,16 @@ def hash_password(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') ->
match algorithm:
case 'md5':
password = md5(password.encode()).hexdigest()
return password
hashed_password = md5(password.encode()).hexdigest()
return hashed_password
case 'sha3_512':
password = sha3_512(password.encode()).hexdigest()
return password
hashed_password = sha3_512(password.encode()).hexdigest()
return hashed_password
case _:
password = md5(password.encode()).hexdigest()
return password
hashed_password = md5(password.encode()).hexdigest()
return hashed_password
def get_all_modules() -> list[str]:
"""Get list of all main modules
@@ -225,9 +224,9 @@ def clean_uid(uid: str) -> Optional[str]:
return None
pattern = fr'[:|@|%|\+|~|\*]*'
parsed_UID = sub(pattern, '', uid)
parsed_uid = sub(pattern, '', uid)
return parsed_UID
return parsed_uid
def hide_sensitive_data(srvmsg: list[str]) -> list[str]:
try:
@@ -243,4 +242,4 @@ def hide_sensitive_data(srvmsg: list[str]) -> list[str]:
return srv_msg
except ValueError:
return srvmsg
return srvmsg

View File

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

View File

@@ -1,90 +1,29 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional, Any
from core.classes.interfaces.imodule import IModule
import mods.clone.utils as utils
import mods.clone.threads as thds
import mods.clone.schemas as schemas
from mods.clone.clone_manager import CloneManager
if TYPE_CHECKING:
from core.irc import Irc
from faker import Faker
class Clone:
class Clone(IModule):
def __init__(self, irc_instance: 'Irc') -> None:
@dataclass
class ModConfModel(schemas.ModConfModel):
...
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
MOD_HEADER: dict[str, str] = {
'name':'Clone',
'version':'1.0.0',
'description':'Connect thousands of clones to your IRCD, by group. You can use them as security moderation.',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Add Irc Object to the module (Mandatory)
self.Irc = irc_instance
# Add Irc Protocol Object to the module (Mandatory)
self.Protocol = irc_instance.Protocol
# Add Global Configuration to the module (Mandatory)
self.Config = irc_instance.Config
# Add Base object to the module (Mandatory)
self.Base = irc_instance.Base
# Add logs object to the module (Mandatory)
self.Logs = irc_instance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = irc_instance.User
# Add Channel object to the module (Mandatory)
self.Channel = irc_instance.Channel
# Add global definitions
self.Definition = irc_instance.Loader.Definition
# The Global Settings
self.Settings = irc_instance.Loader.Settings
self.Schemas = schemas
self.Utils = utils
self.Threads = thds
self.Faker: Optional['Faker'] = self.Utils.create_faker_object('en_GB')
self.Clone = CloneManager(self)
metadata = self.Settings.get_cache('UID_CLONE_DB')
if metadata is not None:
self.Clone.UID_CLONE_DB = metadata
self.Logs.debug(f"Cache Size = {self.Settings.get_cache_size()}")
# Créer les nouvelles commandes du module
self.Irc.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones')
# Init the module (Mandatory)
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Créer les tables necessaire a votre module (ce n'es pas obligatoire)
self.__create_tables()
self.stop = False
# Load module configuration (Mandatory)
self.__load_module_configuration()
self.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send_join_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL)
self.Protocol.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.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
@@ -104,20 +43,28 @@ class Clone:
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Variable qui va contenir les options de configuration du module Defender
self.ModConfig = self.Schemas.ModConfModel()
def load(self) -> None:
self.ModConfig = self.ModConfModel()
self.stop = False
self.Schemas = schemas
self.Utils = utils
self.Threads = thds
self.Faker: Optional['Faker'] = self.Utils.create_faker_object('en_GB')
self.Clone = CloneManager(self)
metadata = self.Settings.get_cache('UID_CLONE_DB')
# Sync the configuration with core configuration (Mandatory)
# self.Base.db_sync_core_config(self.module_name, self.ModConfig)
if metadata is not None:
self.Clone.UID_CLONE_DB = metadata
self.Logs.debug(f"Cache Size = {self.Settings.get_cache_size()}")
return None
# Créer les nouvelles commandes du module
self.Irc.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones')
except TypeError as te:
self.Logs.critical(te)
self.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=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)
def unload(self) -> None:
"""Cette methode sera executée a chaque désactivation ou
@@ -127,8 +74,8 @@ class Clone:
self.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB)
self.Channel.db_query_channel(action='del', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -nts")
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -k {self.Config.CLONE_CHANNEL_PASSWORD}")
self.Protocol.send_set_mode('-nts', channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send_set_mode('-k', channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send_part_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL)
self.Irc.Commands.drop_command_by_module(self.module_name)
@@ -148,7 +95,8 @@ class Clone:
match command:
case 'PRIVMSG':
return self.Utils.handle_on_privmsg(self, cmd)
self.Utils.handle_on_privmsg(self, cmd)
return None
case 'QUIT':
return None
@@ -202,7 +150,7 @@ class Clone:
except Exception as 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")
case 'kill':
@@ -232,8 +180,7 @@ class Clone:
except Exception as 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 clone_nickname")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group name | nickname]")
case 'join':
try:
@@ -262,8 +209,7 @@ class Clone:
except Exception as 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 clone_nickname #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group name | nickname] #channel")
case 'part':
try:
@@ -293,8 +239,7 @@ class Clone:
except Exception as 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 clone_nickname #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group name | nickname] #channel")
case 'list':
try:

View File

@@ -174,17 +174,15 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
return True
def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]):
def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]) -> None:
uid_sender = uplink.Irc.Utils.clean_uid(srvmsg[1])
senderObj = uplink.User.get_user(uid_sender)
senderObj, recieverObj, channel, message = uplink.Protocol.parse_privmsg(srvmsg)
if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT:
return
if not senderObj is None:
senderMsg = ' '.join(srvmsg[4:])
clone_obj = uplink.Clone.get_clone(srvmsg[3])
if senderObj is not None:
if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT:
return
senderMsg = message
clone_obj = recieverObj
if clone_obj is None:
return
@@ -196,3 +194,5 @@ def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]):
msg=final_message,
channel=uplink.Config.CLONE_CHANNEL
)
return None

View File

@@ -1,13 +1,12 @@
from typing import Optional, TYPE_CHECKING
from dataclasses import dataclass
from core.classes.interfaces.imodule import IModule
import mods.command.utils as utils
if TYPE_CHECKING:
from core.irc import Irc
from core.definition import MUser
from sqlalchemy import CursorResult, Row, Sequence
class Command:
class Command(IModule):
@dataclass
class ModConfModel:
@@ -15,54 +14,62 @@ class Command:
"""
pass
def __init__(self, ircInstance: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'Command',
'version':'1.0.0',
'description':'Module contains all IRC commands',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
def create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
Returns:
None: Aucun retour n'es attendu
"""
# Add Loader Object to the module (Mandatory)
self.Loader = ircInstance.Loader
table_automode = '''CREATE TABLE IF NOT EXISTS command_automode (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on TEXT,
updated_on TEXT,
nickname TEXT,
channel TEXT,
mode TEXT
)
'''
# Add Protocol object to the module (Mandatory)
self.Protocol = ircInstance.Protocol
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
# Add main Utils to the module
self.MainUtils = ircInstance.Utils
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = ircInstance.User
# Add Client object to the module (Mandatory)
self.Client = ircInstance.Client
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
self.Base.db_execute_query(table_automode)
return None
def load(self) -> None:
# Module Utils
self.mod_utils = utils
self.Irc.build_command(1, self.module_name, 'join', 'Join a channel')
self.Irc.build_command(1, self.module_name, 'assign', 'Assign a user to a role or task')
self.Irc.build_command(1, self.module_name, 'part', 'Leave a channel')
self.Irc.build_command(1, self.module_name, 'unassign', 'Remove a user from a role or task')
self.Irc.build_command(1, self.module_name, 'owner', 'Give channel ownership to a user')
self.Irc.build_command(1, self.module_name, 'deowner', 'Remove channel ownership from a user')
self.Irc.build_command(1, self.module_name, 'protect', 'Protect a user from being kicked')
self.Irc.build_command(1, self.module_name, 'deprotect', 'Remove protection from a user')
self.Irc.build_command(1, self.module_name, 'op', 'Grant operator privileges to a user')
self.Irc.build_command(1, self.module_name, 'deop', 'Remove operator privileges from a user')
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel()
self.user_to_notice: str = ''
self.show_219: bool = True
# Register new commands into the protocol
new_cmds = {'403', '401', '006', '018', '219', '223'}
for c in new_cmds:
self.Irc.Protocol.known_protocol.add(c)
self.Irc.build_command(2, self.module_name, 'join', 'Join a channel')
self.Irc.build_command(2, self.module_name, 'assign', 'Assign a user to a role or task')
self.Irc.build_command(2, self.module_name, 'part', 'Leave a channel')
self.Irc.build_command(2, self.module_name, 'unassign', 'Remove a user from a role or task')
self.Irc.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user')
self.Irc.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user')
self.Irc.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked')
self.Irc.build_command(2, self.module_name, 'deprotect', 'Remove protection from a user')
self.Irc.build_command(2, self.module_name, 'op', 'Grant operator privileges to a user')
self.Irc.build_command(2, self.module_name, 'deop', 'Remove operator privileges from a user')
self.Irc.build_command(1, self.module_name, 'halfop', 'Grant half-operator privileges to a user')
self.Irc.build_command(1, self.module_name, 'dehalfop', 'Remove half-operator privileges from a user')
self.Irc.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user')
@@ -104,73 +111,6 @@ class Command:
self.Irc.build_command(2, self.module_name, 'klinelist', 'List all K-line bans')
self.Irc.build_command(3, self.module_name, 'map', 'Show the server network map')
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'-- Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Create you own tables (Mandatory)
self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
self.user_to_notice: str = ''
self.show_219: bool = True
return None
def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
Returns:
None: Aucun retour n'es attendu
"""
table_automode = '''CREATE TABLE IF NOT EXISTS command_automode (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on TEXT,
updated_on TEXT,
nickname TEXT,
channel TEXT,
mode TEXT
)
'''
self.Base.db_execute_query(table_automode)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel()
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
return None
except TypeError as te:
self.Logs.critical(te)
def __update_configuration(self, param_key: str, param_value: str):
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
def unload(self) -> None:
self.Irc.Commands.drop_command_by_module(self.module_name)
return None
@@ -186,10 +126,12 @@ class Command:
nogc = self.Config.COLORS.nogc
cmd = list(data).copy()
if len(cmd) < 2:
pos, parsed_cmd = self.Irc.Protocol.get_ircd_protocol_poisition(cmd=cmd, log=True)
if pos == -1:
return None
match cmd[1]:
match parsed_cmd:
# [':irc.deb.biz.st', '403', 'Dev-PyDefender', '#Z', ':No', 'such', 'channel']
case '403' | '401':
try:
@@ -260,22 +202,10 @@ class Command:
except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}')
case _:
pass
if len(cmd) < 3:
return None
match cmd[2]:
case 'SJOIN':
# ['@msgid=yldTlbwAGbzCGUcCIHi3ku;time=2024-11-11T17:56:24.297Z', ':001', 'SJOIN', '1728815963', '#znc', ':001LQ0L0C']
# Check if the user has an automode
try:
if len(cmd) < 6:
return None
user_uid = self.User.clean_uid(cmd[5])
userObj: MUser = self.User.get_user(user_uid)
channel_name = cmd[4] if self.Channel.is_valid_channel(cmd[4]) else None
@@ -301,6 +231,9 @@ class Command:
except KeyError as ke:
self.Logs.error(f"Key Error: {err}")
case _:
pass
except Exception as err:
self.Logs.error(f"General Error: {err}")

View File

@@ -134,17 +134,20 @@ def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str]
return False
if len(cmd) == 1:
uplink.Protocol.send2socket(f":{dnickname} MODE {channel_name} {mode} {client}")
# uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {client}")
uplink.Protocol.send_set_mode(mode, nickname=client, channel_name=channel_name)
return None
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
# uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
uplink.Protocol.send_set_mode(mode, nickname=nickname, channel_name=channel_name)
return None
nickname = cmd[2]
uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
# uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
uplink.Protocol.send_set_mode(mode, nickname=nickname, channel_name=channel_name)
return None
def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None:

View File

@@ -1,129 +1,26 @@
from typing import TYPE_CHECKING
from dataclasses import dataclass
from typing import Any
from core.classes.interfaces.imodule import IModule
import mods.defender.schemas as schemas
import mods.defender.utils as utils
import mods.defender.threads as thds
from core.utils import tr
if TYPE_CHECKING:
from core.irc import Irc
class Defender(IModule):
class Defender:
@dataclass
class ModConfModel(schemas.ModConfModel):
...
def __init__(self, irc_instance: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'Defender',
'version':'1.0.0',
'description':'Defender main module that uses the reputation security.',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory)
self.Irc = irc_instance
# Add Loader Object to the module (Mandatory)
self.Loader = irc_instance.Loader
# Add server protocol Object to the module (Mandatory)
self.Protocol = irc_instance.Protocol
# Add Global Configuration to the module (Mandatory)
self.Config = irc_instance.Config
# Add Base object to the module (Mandatory)
self.Base = irc_instance.Base
# Add logs object to the module (Mandatory)
self.Logs = irc_instance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = irc_instance.User
# Add Channel object to the module (Mandatory)
self.Channel = irc_instance.Channel
# Add Settings object to save objects when reloading modules (Mandatory)
self.Settings = irc_instance.Settings
# Add Reputation object to the module (Optional)
self.Reputation = irc_instance.Reputation
# Add module schemas
self.Schemas = schemas
# Add utils functions
self.Utils = utils
# Create module commands (Mandatory)
self.Irc.build_command(0, self.module_name, 'code', 'Display the code or key for access')
self.Irc.build_command(1, self.module_name, 'info', 'Provide information about the channel or server')
self.Irc.build_command(1, self.module_name, 'autolimit', 'Automatically set channel user limits')
self.Irc.build_command(3, self.module_name, 'reputation', 'Check or manage user reputation')
self.Irc.build_command(3, self.module_name, 'proxy_scan', 'Scan users for proxy connections')
self.Irc.build_command(3, self.module_name, 'flood', 'Handle flood detection and mitigation')
self.Irc.build_command(3, self.module_name, 'status', 'Check the status of the server or bot')
self.Irc.build_command(3, self.module_name, 'timer', 'Set or manage timers')
self.Irc.build_command(3, self.module_name, 'show_reputation', 'Display reputation information')
self.Irc.build_command(3, self.module_name, 'sentinel', 'Monitor and guard the channel or server')
# Init the module (Mandatory)
self.__init_module()
# Log the module
self.Logs.debug(f'-- Module {self.module_name} V2 loaded ...')
def __init_module(self) -> None:
# Create you own tables if needed (Mandatory)
self.__create_tables()
# Load module configuration (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
self.timeout = self.Config.API_TIMEOUT
# Listes qui vont contenir les ip a scanner avec les différentes API
self.Schemas.DB_ABUSEIPDB_USERS = []
self.Schemas.DB_FREEIPAPI_USERS = []
self.Schemas.DB_CLOUDFILT_USERS = []
self.Schemas.DB_PSUTIL_USERS = []
self.Schemas.DB_LOCALSCAN_USERS = []
# Variables qui indique que les threads sont en cours d'éxecutions
self.abuseipdb_isRunning:bool = True
self.freeipapi_isRunning:bool = True
self.cloudfilt_isRunning:bool = True
self.psutil_isRunning:bool = True
self.localscan_isRunning:bool = True
self.reputationTimer_isRunning:bool = True
self.autolimit_isRunning: bool = True
# Variable qui va contenir les users
self.flood_system = {}
# Contient les premieres informations de connexion
self.reputation_first_connexion = {'ip': '', 'score': -1}
# Laisser vide si aucune clé
self.abuseipdb_key = '13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674'
self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg'
# Démarrer les threads pour démarrer les api
self.Base.create_thread(func=thds.thread_freeipapi_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_cloudfilt_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_abuseipdb_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_local_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_psutil_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_apply_reputation_sanctions, func_args=(self, ))
if self.ModConfig.autolimit == 1:
self.Base.create_thread(func=thds.thread_autolimit, func_args=(self, ))
if self.ModConfig.reputation == 1:
self.Protocol.send_sjoin(self.Config.SALON_JAIL)
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {self.Config.SALON_JAIL} +o {self.Config.SERVICE_NICKNAME}")
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.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
@@ -146,20 +43,63 @@ class Defender:
# self.Base.db_execute_query(table_trusted)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
def load(self):
# Add module schemas
self.Schemas = schemas
# Add utils functions
self.Utils = utils
# Variable qui va contenir les options de configuration du module Defender
self.ModConfig = self.Schemas.ModConfModel()
self.ModConfig: schemas.ModConfModel = self.ModConfModel()
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
# Create module commands (Mandatory)
self.Irc.build_command(0, self.module_name, 'code', 'Display the code or key for access')
self.Irc.build_command(1, self.module_name, 'info', 'Provide information about the channel or server')
self.Irc.build_command(1, self.module_name, 'autolimit', 'Automatically set channel user limits')
self.Irc.build_command(3, self.module_name, 'reputation', 'Check or manage user reputation')
self.Irc.build_command(3, self.module_name, 'proxy_scan', 'Scan users for proxy connections')
self.Irc.build_command(3, self.module_name, 'flood', 'Handle flood detection and mitigation')
self.Irc.build_command(3, self.module_name, 'status', 'Check the status of the server or bot')
self.Irc.build_command(3, self.module_name, 'show_reputation', 'Display reputation information')
self.Irc.build_command(3, self.module_name, 'sentinel', 'Monitor and guard the channel or server')
return None
self.timeout = self.Config.API_TIMEOUT
def __update_configuration(self, param_key: str, param_value: str):
# Listes qui vont contenir les ip a scanner avec les différentes API
self.Schemas.DB_ABUSEIPDB_USERS = self.Schemas.DB_FREEIPAPI_USERS = self.Schemas.DB_CLOUDFILT_USERS = []
self.Schemas.DB_PSUTIL_USERS = self.Schemas.DB_LOCALSCAN_USERS = []
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
# Variables qui indique que les threads sont en cours d'éxecutions
self.abuseipdb_isRunning = self.freeipapi_isRunning = self.cloudfilt_isRunning = True
self.psutil_isRunning = self.localscan_isRunning = self.reputationTimer_isRunning = True
self.autolimit_isRunning = True
# Variable qui va contenir les users
self.flood_system = {}
# Contient les premieres informations de connexion
self.reputation_first_connexion = {'ip': '', 'score': -1}
# Laisser vide si aucune clé
self.abuseipdb_key = '13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674'
self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg'
# Démarrer les threads pour démarrer les api
self.Base.create_thread(func=thds.thread_freeipapi_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_cloudfilt_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_abuseipdb_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_local_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_psutil_scan, func_args=(self, ))
self.Base.create_thread(func=thds.thread_apply_reputation_sanctions, func_args=(self, ))
if self.ModConfig.autolimit == 1:
self.Base.create_thread(func=thds.thread_autolimit, func_args=(self, ))
if self.ModConfig.reputation == 1:
self.Protocol.send_sjoin(self.Config.SALON_JAIL)
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {self.Config.SALON_JAIL} +o {self.Config.SERVICE_NICKNAME}")
def __onload(self):
@@ -207,9 +147,12 @@ class Defender:
return None
def insert_db_trusted(self, uid: str, nickname:str) -> None:
u = self.User.get_user(uid)
if u is None:
return None
uid = self.User.get_uid(uid)
nickname = self.User.get_nickname(nickname)
uid = u.uid
nickname = u.nickname
query = "SELECT id FROM def_trusted WHERE user = ?"
exec_query = self.Base.db_execute_query(query, {"user": nickname})
@@ -217,7 +160,7 @@ class Defender:
if response is not None:
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)
pass
@@ -245,29 +188,6 @@ class Defender:
except Exception as err:
self.Logs.error(f"General Error: {err}")
def run_db_action_timer(self, wait_for: float = 0) -> None:
query = f"SELECT param_key FROM {self.Config.TABLE_CONFIG}"
res = self.Base.db_execute_query(query)
service_id = self.Config.SERVICE_ID
dchanlog = self.Config.SERVICE_CHANLOG
for param in res.fetchall():
if param[0] == 'reputation':
self.Protocol.send_priv_msg(
nick_from=service_id,
msg=f" ===> {param[0]}",
channel=dchanlog
)
else:
self.Protocol.send_priv_msg(
nick_from=service_id,
msg=f"{param[0]}",
channel=dchanlog
)
return None
def cmd(self, data: list[str]) -> None:
if not data or len(data) < 2:
@@ -298,7 +218,6 @@ class Defender:
return None
case 'SJOIN':
self.Utils.handle_on_sjoin(self, cmd)
return None
@@ -324,10 +243,13 @@ class Defender:
except Exception as err:
self.Logs.error(f"General Error: {err}", exc_info=True)
def hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None:
u = self.User.get_user(user)
if u is None:
return None
command = str(cmd[0]).lower()
fromuser = user
fromuser = u.nickname
channel = fromchannel = channel if self.Channel.is_valid_channel(channel) else None
dnickname = self.Config.SERVICE_NICKNAME # Defender nickname
@@ -339,17 +261,6 @@ class Defender:
match command:
case 'timer':
try:
timer_sent = self.Base.int_if_possible(cmd[1])
timer_sent = int(timer_sent)
self.Base.create_timer(timer_sent, self.run_db_action_timer)
except TypeError as te:
self.Logs.error(f"Type Error -> {te}")
except ValueError as ve:
self.Logs.error(f"Value Error -> {ve}")
case 'show_reputation':
if not self.Reputation.UID_REPUTATION_DB:
@@ -363,9 +274,9 @@ class Defender:
case 'code':
try:
release_code = cmd[1]
jailed_nickname = self.User.get_nickname(fromuser)
jailed_UID = self.User.get_uid(fromuser)
get_reputation = self.Reputation.get_Reputation(jailed_UID)
jailed_nickname = u.nickname
jailed_UID = u.uid
get_reputation = self.Reputation.get_reputation(jailed_UID)
if get_reputation is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" No code is requested ...")
@@ -394,7 +305,7 @@ class Defender:
self.Protocol.send_sapart(nick_to_sapart=jailed_nickname, channel_name=jailed_salon)
self.Protocol.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=welcome_salon)
self.Protocol.send2socket(f":{link} REPUTATION {jailed_IP} {self.ModConfig.reputation_score_after_release}")
self.User.get_user(jailed_UID).score_connexion = reputation_seuil + 1
u.score_connexion = reputation_seuil + 1
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[{color_green} MOT DE PASS CORRECT {color_black}] : You have now the right to enjoy the network !",
nick_to=jailed_nickname)
@@ -431,7 +342,7 @@ class Defender:
match arg:
case 'on':
if self.ModConfig.autolimit == 0:
self.__update_configuration('autolimit', 1)
self.update_configuration('autolimit', 1)
self.autolimit_isRunning = True
self.Base.create_thread(func=thds.thread_autolimit, func_args=(self, ))
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.Config.COLORS.green}AUTOLIMIT{self.Config.COLORS.nogc}] Activated", channel=self.Config.SERVICE_CHANLOG)
@@ -440,7 +351,7 @@ class Defender:
case 'off':
if self.ModConfig.autolimit == 1:
self.__update_configuration('autolimit', 0)
self.update_configuration('autolimit', 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.Config.COLORS.green}AUTOLIMIT{self.Config.COLORS.nogc}] Deactivated", channel=self.Config.SERVICE_CHANLOG)
else:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.Config.COLORS.red}AUTOLIMIT{self.Config.COLORS.nogc}] Already Deactivated", channel=self.Config.SERVICE_CHANLOG)
@@ -449,8 +360,8 @@ class Defender:
amount = int(cmd[2])
interval = int(cmd[3])
self.__update_configuration('autolimit_amount', amount)
self.__update_configuration('autolimit_interval', interval)
self.update_configuration('autolimit_amount', amount)
self.update_configuration('autolimit_interval', interval)
self.Protocol.send_priv_msg(
nick_from=dnickname,
msg=f"[{self.Config.COLORS.green}AUTOLIMIT{self.Config.COLORS.nogc}] Amount set to ({amount}) | Interval set to ({interval})",
@@ -489,7 +400,7 @@ class Defender:
return False
# self.update_db_configuration('reputation', 1)
self.__update_configuration(key, 1)
self.update_configuration(key, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.Config.COLORS.green}REPUTATION{self.Config.COLORS.black} ] : Activated by {fromuser}", channel=dchanlog)
@@ -515,7 +426,7 @@ class Defender:
)
return False
self.__update_configuration(key, 0)
self.update_configuration(key, 0)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -551,7 +462,7 @@ class Defender:
msg=f"This nickname ({str(cmd[2])}) is not connected to the network!")
return None
client_to_release = self.Reputation.get_Reputation(client_obj.uid)
client_to_release = self.Reputation.get_reputation(client_obj.uid)
if client_to_release is None:
p.send_notice(nick_from=dnickname,
@@ -604,7 +515,7 @@ class Defender:
return False
# self.update_db_configuration(key, 1)
self.__update_configuration(key, 1)
self.update_configuration(key, 1)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -622,7 +533,7 @@ class Defender:
return False
# self.update_db_configuration(key, 0)
self.__update_configuration(key, 0)
self.update_configuration(key, 0)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -635,7 +546,7 @@ class Defender:
key = 'reputation_seuil'
# self.update_db_configuration(key, reputation_seuil)
self.__update_configuration(key, reputation_seuil)
self.update_configuration(key, reputation_seuil)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -647,7 +558,7 @@ class Defender:
case 'timer':
reputation_timer = int(cmd[3])
key = 'reputation_timer'
self.__update_configuration(key, reputation_timer)
self.update_configuration(key, reputation_timer)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -659,7 +570,7 @@ class Defender:
case 'score_after_release':
reputation_score_after_release = int(cmd[3])
key = 'reputation_score_after_release'
self.__update_configuration(key, reputation_score_after_release)
self.update_configuration(key, reputation_score_after_release)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -671,7 +582,7 @@ class Defender:
case 'security_group':
reputation_sg = int(cmd[3])
key = 'reputation_sg'
self.__update_configuration(key, reputation_sg)
self.update_configuration(key, reputation_sg)
self.Protocol.send_priv_msg(
nick_from=dnickname,
@@ -733,7 +644,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
return None
self.__update_configuration(option, 1)
self.update_configuration(option, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
elif action == 'off':
@@ -741,7 +652,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
return None
self.__update_configuration(option, 0)
self.update_configuration(option, 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
@@ -751,7 +662,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
return None
self.__update_configuration(option, 1)
self.update_configuration(option, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
elif action == 'off':
@@ -759,7 +670,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
return None
self.__update_configuration(option, 0)
self.update_configuration(option, 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
@@ -769,7 +680,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
return None
self.__update_configuration(option, 1)
self.update_configuration(option, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
elif action == 'off':
@@ -777,7 +688,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
return None
self.__update_configuration(option, 0)
self.update_configuration(option, 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
@@ -787,7 +698,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
return None
self.__update_configuration(option, 1)
self.update_configuration(option, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
elif action == 'off':
@@ -795,7 +706,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
return None
self.__update_configuration(option, 0)
self.update_configuration(option, 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
@@ -805,7 +716,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
return None
self.__update_configuration(option, 1)
self.update_configuration(option, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
elif action == 'off':
@@ -813,7 +724,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
return None
self.__update_configuration(option, 0)
self.update_configuration(option, 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
@@ -846,7 +757,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.Config.COLORS.green}FLOOD{self.Config.COLORS.black} ] : Already activated", channel=dchanlog)
return False
self.__update_configuration(key, 1)
self.update_configuration(key, 1)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.Config.COLORS.green}FLOOD{self.Config.COLORS.black} ] : Activated by {fromuser}", channel=dchanlog)
@@ -855,7 +766,7 @@ class Defender:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.Config.COLORS.red}FLOOD{self.Config.COLORS.black} ] : Already Deactivated", channel=dchanlog)
return False
self.__update_configuration(key, 0)
self.update_configuration(key, 0)
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.Config.COLORS.green}FLOOD{self.Config.COLORS.black} ] : Deactivated by {fromuser}", channel=dchanlog)
@@ -867,7 +778,7 @@ class Defender:
case 'flood_message':
key = 'flood_message'
set_value = int(cmd[3])
self.__update_configuration(key, set_value)
self.update_configuration(key, set_value)
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {self.Config.COLORS.green}FLOOD{self.Config.COLORS.black} ] : Flood message set to {set_value} by {fromuser}",
@@ -876,7 +787,7 @@ class Defender:
case 'flood_time':
key = 'flood_time'
set_value = int(cmd[3])
self.__update_configuration(key, set_value)
self.update_configuration(key, set_value)
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {self.Config.COLORS.green}FLOOD{self.Config.COLORS.black} ] : Flood time set to {set_value} by {fromuser}",
@@ -885,7 +796,7 @@ class Defender:
case 'flood_timer':
key = 'flood_timer'
set_value = int(cmd[3])
self.__update_configuration(key, set_value)
self.update_configuration(key, set_value)
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[ {self.Config.COLORS.green}FLOOD{self.Config.COLORS.black} ] : Flood timer set to {set_value} by {fromuser}",
@@ -922,6 +833,7 @@ class Defender:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' flood_message ==> {self.ModConfig.flood_message}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' flood_time ==> {self.ModConfig.flood_time}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' flood_timer ==> {self.ModConfig.flood_timer}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' [{color_green if self.ModConfig.flood == 1 else color_red}Sentinel{nogc}] ==> {self.ModConfig.sentinel}')
except KeyError as ke:
self.Logs.error(f"Key Error : {ke}")
@@ -949,7 +861,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' 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' 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}')
else:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {nickoruid} doesn't exist")
@@ -963,14 +875,27 @@ class Defender:
channel_to_dont_quit = [self.Config.SALON_JAIL, self.Config.SERVICE_CHANLOG]
if activation == 'on':
result = self.Base.db_execute_query(f"SELECT distinct channel_name FROM {self.Config.TABLE_CHANNEL}")
channels = result.fetchall()
channel_in_db = [channel[0] for channel in channels]
channel_to_dont_quit.extend(channel_in_db)
self.update_configuration('sentinel', 1)
for chan in self.Channel.UID_CHANNEL_DB:
if chan.name not in channel_to_dont_quit:
self.Protocol.send_join_chan(uidornickname=dnickname, channel=chan.name)
self.Protocol.send_priv_msg(dnickname, f"Sentinel mode activated on {channel}", channel=chan.name)
return None
if activation == 'off':
result = self.Base.db_execute_query(f"SELECT distinct channel_name FROM {self.Config.TABLE_CHANNEL}")
channels = result.fetchall()
channel_in_db = [channel[0] for channel in channels]
channel_to_dont_quit.extend(channel_in_db)
self.update_configuration('sentinel', 0)
for chan in self.Channel.UID_CHANNEL_DB:
if chan.name not in channel_to_dont_quit:
self.Protocol.send_part_chan(uidornickname=dnickname, channel=chan.name)
self.Protocol.send_priv_msg(dnickname, f"Sentinel mode deactivated on {channel}", channel=chan.name)
self.join_saved_channels()
return None

View File

@@ -20,6 +20,7 @@ class ModConfModel(MainModel):
autolimit: int = 0
autolimit_amount: int = 3
autolimit_interval: int = 3
sentinel: int = 0
@dataclass
class FloodUser(MainModel):

View File

@@ -62,6 +62,11 @@ def handle_on_mode(uplink: 'Defender', srvmsg: list[str]):
def handle_on_privmsg(uplink: 'Defender', srvmsg: list[str]):
# ['@mtag....',':python', 'PRIVMSG', '#defender', ':zefzefzregreg', 'regg', 'aerg']
sender, reciever, channel, message = uplink.Protocol.parse_privmsg(srvmsg)
if uplink.ModConfig.sentinel == 1 and channel.name != uplink.Config.SERVICE_CHANLOG:
uplink.Protocol.send_priv_msg(uplink.Config.SERVICE_NICKNAME, f"{sender.nickname} say on {channel.name}: {' '.join(message)}", uplink.Config.SERVICE_CHANLOG)
action_on_flood(uplink, srvmsg)
return None
@@ -87,7 +92,7 @@ def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]):
return
if confmodel.reputation == 1:
get_reputation = irc.Reputation.get_Reputation(parsed_UID)
get_reputation = irc.Reputation.get_reputation(parsed_UID)
if parsed_chan != gconfig.SALON_JAIL:
p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b ~security-group:unknown-users")
@@ -138,18 +143,25 @@ def handle_on_slog(uplink: 'Defender', srvmsg: list[str]):
return None
def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
"""_summary_
"""Handle nickname changes.
>>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'newnickname', '1754663712']
>>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740']
Args:
irc_instance (Irc): The Irc instance
srvmsg (list[str]): The Server MSG
confmodel (ModConfModel): The Module Configuration
"""
uid = uplink.Loader.Utils.clean_uid(str(srvmsg[1]))
p = uplink.Protocol
u, new_nickname, timestamp = p.parse_nick(srvmsg)
if u is None:
uplink.Logs.error(f"[USER OBJ ERROR {timestamp}] - {srvmsg}")
return None
uid = u.uid
confmodel = uplink.ModConfig
get_reputation = uplink.Reputation.get_Reputation(uid)
get_reputation = uplink.Reputation.get_reputation(uid)
jail_salon = uplink.Config.SALON_JAIL
service_id = uplink.Config.SERVICE_ID
@@ -159,7 +171,7 @@ def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
# Update the new nickname
oldnick = get_reputation.nickname
newnickname = srvmsg[3]
newnickname = new_nickname
get_reputation.nickname = newnickname
# If ban in all channel is ON then unban old nickname an ban the new nickname
@@ -170,20 +182,24 @@ def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*")
def handle_on_quit(uplink: 'Defender', srvmsg: list[str]):
"""_summary_
"""Handle on quit message
>>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', 'quit message']
Args:
uplink (Irc): The Defender Module instance
srvmsg (list[str]): The Server MSG
"""
p = uplink.Protocol
userobj, reason = p.parse_quit(srvmsg)
confmodel = uplink.ModConfig
if userobj is None:
uplink.Logs.debug(f"This UID do not exist anymore: {srvmsg}")
return None
ban_all_chan = uplink.Base.int_if_possible(confmodel.reputation_ban_all_chan)
final_UID = uplink.Loader.Utils.clean_uid(str(srvmsg[1]))
jail_salon = uplink.Config.SALON_JAIL
service_id = uplink.Config.SERVICE_ID
get_user_reputation = uplink.Reputation.get_Reputation(final_UID)
get_user_reputation = uplink.Reputation.get_reputation(userobj.uid)
if get_user_reputation is not None:
final_nickname = get_user_reputation.nickname
@@ -192,7 +208,7 @@ def handle_on_quit(uplink: 'Defender', srvmsg: list[str]):
p.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*")
uplink.Logs.debug(f"Mode -b {final_nickname} on channel {chan.name}")
uplink.Reputation.delete(final_UID)
uplink.Reputation.delete(userobj.uid)
uplink.Logs.debug(f"Client {get_user_reputation.nickname} has been removed from Reputation local DB")
def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
@@ -204,6 +220,7 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
uplink (Defender): The Defender instance
srvmsg (list[str]): The Server MSG
"""
_User = uplink.Protocol.parse_uid(srvmsg)
gconfig = uplink.Config
irc = uplink.Irc
confmodel = uplink.ModConfig
@@ -213,10 +230,8 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
return None
# Get User information
_User = irc.User.get_user(str(srvmsg[8]))
if _User is None:
irc.Logs.warning(f'This UID: [{srvmsg[8]}] is not available please check why')
irc.Logs.warning(f'Error when parsing UID', exc_info=True)
return
# If user is not service or IrcOp then scan them
@@ -249,7 +264,8 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
####################
# 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]):
confmodel = uplink.ModConfig
@@ -318,7 +334,7 @@ def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
p = uplink.Protocol
confmodel = uplink.ModConfig
get_reputation = irc.Reputation.get_Reputation(jailed_uid)
get_reputation = irc.Reputation.get_reputation(jailed_uid)
if get_reputation is None:
irc.Logs.warning(f'UID {jailed_uid} has not been found')
@@ -378,14 +394,11 @@ def action_apply_reputation_santions(uplink: 'Defender') -> None:
color_red = gconfig.COLORS.red
nogc = gconfig.COLORS.nogc
salon_jail = gconfig.SALON_JAIL
if reputation_flag == 0:
return None
elif reputation_timer == 0:
return None
uid_to_clean = []
if reputation_flag == 0 or reputation_timer == 0 or not irc.Reputation.UID_REPUTATION_DB:
return None
for user in irc.Reputation.UID_REPUTATION_DB:
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):
@@ -404,7 +417,7 @@ def action_apply_reputation_santions(uplink: 'Defender') -> None:
# Suppression des éléments dans {UID_DB} et {REPUTATION_DB}
for chan in irc.Channel.UID_CHANNEL_DB:
if chan.name != salon_jail and ban_all_chan == 1:
get_user_reputation = irc.Reputation.get_Reputation(uid)
get_user_reputation = irc.Reputation.get_reputation(uid)
p.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*")
# Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}.

View File

@@ -1,18 +1,13 @@
import logging
import asyncio
from unrealircd_rpc_py.objects.Definition import LiveRPCResult
from core.classes.interfaces.imodule import IModule
import mods.jsonrpc.utils as utils
import mods.jsonrpc.threads as thds
from time import sleep
from types import SimpleNamespace
from typing import TYPE_CHECKING
from dataclasses import dataclass
from unrealircd_rpc_py.Live import LiveWebsocket, LiveUnixSocket
from unrealircd_rpc_py.Loader import Loader
from unrealircd_rpc_py.ConnectionFactory import ConnectionFactory
from unrealircd_rpc_py.LiveConnectionFactory import LiveConnectionFactory
if TYPE_CHECKING:
from core.irc import Irc
class Jsonrpc():
class Jsonrpc(IModule):
@dataclass
class ModConfModel:
@@ -20,34 +15,62 @@ class Jsonrpc():
"""
jsonrpc: int = 0
def __init__(self, ircInstance: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'JsonRPC',
'version':'1.0.0',
'description':'Module using the unrealircd-rpc-py library',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
def callback_sent_to_irc(self, response: LiveRPCResult) -> None:
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
dnickname = self.Config.SERVICE_NICKNAME
dchanlog = self.Config.SERVICE_CHANLOG
green = self.Config.COLORS.green
nogc = self.Config.COLORS.nogc
bold = self.Config.COLORS.bold
red = self.Config.COLORS.red
# Add Protocol to the module (Mandatory)
self.Protocol = ircInstance.Protocol
if response.error.code != 0:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {response.error.message} ({response.error.code})",
channel=dchanlog)
return None
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
if isinstance(response.result, bool):
if response.result:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
channel=dchanlog)
return None
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
level = response.result.level if hasattr(response.result, 'level') else ''
subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else ''
event_id = response.result.event_id if hasattr(response.result, 'event_id') else ''
log_source = response.result.log_source if hasattr(response.result, 'log_source') else ''
msg = response.result.msg if hasattr(response.result, 'msg') else ''
# Add Main Utils (Mandatory)
self.MainUtils = ircInstance.Utils
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
self.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
return None
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Loader.Logs
def create_tables(self) -> None:
return None
# Add User object to the module (Mandatory)
self.User = ircInstance.User
def load(self) -> None:
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
logging.getLogger('websockets').setLevel(logging.WARNING)
logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL)
logging.getLogger('unrealircd-liverpc-py').setLevel(logging.CRITICAL)
self.ModConfig = self.ModConfModel(jsonrpc=0)
if self.Config.SERVEUR_PROTOCOL != 'unreal6':
self.Loader.ModuleUtils.unload_one_module(self.Irc, self.module_name, False)
return None
# Is RPC Active?
self.is_streaming = False
@@ -66,152 +89,42 @@ class Jsonrpc():
self.Irc.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC')
self.Irc.build_command(1, self.module_name, 'jrinstances', 'Get number of instances')
# Init the module
self.__init_module()
try:
self.Rpc = ConnectionFactory(self.Config.DEBUG_LEVEL).get(self.Config.JSONRPC_METHOD)
self.LiveRpc = LiveConnectionFactory(self.Config.DEBUG_LEVEL).get(self.Config.JSONRPC_METHOD)
sync_unixsocket = {'path_to_socket_file': self.Config.JSONRPC_PATH_TO_SOCKET_FILE}
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'}
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
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
def __init_module(self) -> None:
self.Rpc.setup(sync_param)
self.LiveRpc.setup(live_param)
logging.getLogger('websockets').setLevel(logging.WARNING)
logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL)
logging.getLogger('unrealircd-liverpc-py').setLevel(logging.CRITICAL)
# Create you own tables (Mandatory)
# self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
self.UnrealIrcdRpcLive: LiveWebsocket = LiveWebsocket(
url=self.Config.JSONRPC_URL,
username=self.Config.JSONRPC_USER,
password=self.Config.JSONRPC_PASSWORD,
callback_object_instance=self,
callback_method_or_function_name='callback_sent_to_irc'
)
if self.UnrealIrcdRpcLive.get_error.code != 0:
self.Logs.error(f"{self.UnrealIrcdRpcLive.get_error.message} ({self.UnrealIrcdRpcLive.get_error.code})")
if self.ModConfig.jsonrpc == 1:
self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True)
return None
except Exception as err:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}",
msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {err.__str__()}",
channel=self.Config.SERVICE_CHANLOG
)
raise Exception(f"[LIVE-JSONRPC ERROR] {self.UnrealIrcdRpcLive.get_error.message}")
self.Rpc: Loader = Loader(
req_method=self.Config.JSONRPC_METHOD,
url=self.Config.JSONRPC_URL,
username=self.Config.JSONRPC_USER,
password=self.Config.JSONRPC_PASSWORD
)
if self.Rpc.get_error.code != 0:
self.Logs.error(f"{self.Rpc.get_error.message} ({self.Rpc.get_error.code})")
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}",
channel=self.Config.SERVICE_CHANLOG
)
raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}")
if self.ModConfig.jsonrpc == 1:
self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True)
return None
def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
Returns:
None: Aucun retour n'es attendu
"""
table_logs = '''CREATE TABLE IF NOT EXISTS test_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT,
server_msg TEXT
)
'''
self.Base.db_execute_query(table_logs)
return None
def callback_sent_to_irc(self, response: SimpleNamespace) -> None:
dnickname = self.Config.SERVICE_NICKNAME
dchanlog = self.Config.SERVICE_CHANLOG
green = self.Config.COLORS.green
nogc = self.Config.COLORS.nogc
bold = self.Config.COLORS.bold
red = self.Config.COLORS.red
if self.UnrealIrcdRpcLive.get_error.code != 0:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {self.UnrealIrcdRpcLive.get_error.message}",
channel=dchanlog)
return None
if hasattr(response, 'error'):
if response.error.code != 0:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{bold}{red}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
channel=dchanlog)
return None
if hasattr(response, 'result'):
if isinstance(response.result, bool):
if response.result:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
channel=dchanlog)
return None
level = response.result.level if hasattr(response.result, 'level') else ''
subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else ''
event_id = response.result.event_id if hasattr(response.result, 'event_id') else ''
log_source = response.result.log_source if hasattr(response.result, 'log_source') else ''
msg = response.result.msg if hasattr(response.result, 'msg') else ''
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
self.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel(jsonrpc=0)
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
return None
except TypeError as te:
self.Logs.critical(te)
def update_configuration(self, param_key: str, param_value: str) -> None:
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
)
self.Logs.error(f"JSONRPC ERROR: {err.__str__()}")
def unload(self) -> None:
if self.Config.SERVEUR_PROTOCOL != 'unreal6':
self.Loader.ModuleUtils.unload_one_module(self.Irc, self.module_name, False)
return None
if self.is_streaming:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
@@ -278,18 +191,13 @@ class Jsonrpc():
match option:
case 'get':
nickname = str(cmd[2])
uid_to_get = self.User.get_uid(nickname)
if uid_to_get is None:
return None
rpc = self.Rpc
UserInfo = rpc.User.get(uid_to_get)
if rpc.get_error.code != 0:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.get_error.message}')
UserInfo = rpc.User.get(nickname)
if UserInfo.error.code != 0:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{UserInfo.error.message}')
return None
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'UID : {UserInfo.id}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'NICKNAME : {UserInfo.name}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}')
@@ -321,9 +229,8 @@ class Jsonrpc():
case 'jrinstances':
try:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.MainUtils.run_python_garbage_collector()}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveWebsocket)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.MainUtils.get_number_gc_objects(LiveUnixSocket)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.MainUtils.get_number_gc_objects(Loader)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveConnectionFactory)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance ConnectionFactory: {self.MainUtils.get_number_gc_objects(ConnectionFactory)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.MainUtils.get_number_gc_objects()}")
except Exception as err:
self.Logs.error(f"Unknown Error: {err}")

View File

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

View File

@@ -1,75 +1,28 @@
from typing import TYPE_CHECKING
from dataclasses import dataclass, fields
from typing import Any
from core.classes.interfaces.imodule import IModule
from dataclasses import dataclass
if TYPE_CHECKING:
from core.irc import Irc
class Test():
class Test(IModule):
@dataclass
class ModConfModel:
"""The Model containing the module parameters
"""The Model containing the module parameters (Mandatory)
you can leave it without params.
just use pass | if you leave it empty, in the load() method just init empty object ==> self.ModConfig = ModConfModel()
"""
param_exemple1: str
param_exemple2: int
def __init__(self, ircInstance: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'Test',
'version':'1.0.0',
'description':'The test module',
'author':'Defender Team',
'core_version':'Defender-6'
}
"""Module Header (Mandatory)"""
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
# Add Loader Object to the module (Mandatory)
self.Loader = ircInstance.Loader
# Add server protocol Object to the module (Mandatory)
self.Protocol = ircInstance.Protocol
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = ircInstance.User
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
# Add Reputation object to the module (Optional)
self.Reputation = ircInstance.Reputation
# Create module commands (Mandatory)
self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command')
self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command')
self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command')
self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command')
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Create you own tables (Mandatory)
self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
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.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
@@ -86,69 +39,69 @@ class Test():
)
'''
self.Base.db_execute_query(table_logs)
# self.Base.db_execute_query(table_logs)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
def load(self) -> None:
"""### Load Module Configuration (Mandatory)
"""
try:
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel(param_exemple1='param value 1', param_exemple2=1)
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
# Create module commands (Mandatory)
self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command')
self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command')
self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command')
self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command')
return None
except TypeError as te:
self.Logs.critical(te)
def __update_configuration(self, param_key: str, param_value: str):
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel(param_exemple1='str', param_exemple2=1)
def unload(self) -> None:
"""### This method is called when you unload or you reload the module (Mandatory)"""
self.Irc.Commands.drop_command_by_module(self.module_name)
return None
def cmd(self, data:list) -> None:
try:
cmd = list(data).copy()
def cmd(self, data: list[str]) -> None:
"""All messages coming from the IRCD server will be handled using this method (Mandatory)
Args:
data (list): Messages coming from the IRCD server.
"""
cmd = list(data).copy()
try:
return None
except KeyError as ke:
self.Logs.error(f"Key Error: {ke}")
except IndexError as ie:
self.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
except Exception as err:
self.Logs.error(f"General Error: {err}")
def hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None:
"""All messages coming from the user commands (Mandatory)
Args:
user (str): The user who send the request.
channel (Any): The channel from where is coming the message (could be None).
cmd (list): The messages coming from the IRCD server.
fullcmd (list, optional): The full messages coming from the IRCD server. Defaults to [].
"""
u = self.User.get_user(user)
c = self.Channel.get_channel(channel) if self.Channel.is_valid_channel(channel) else None
if u is None:
return None
command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME
fromuser = user
fromchannel = str(channel) if not channel is None else None
match command:
case 'test-command':
try:
self.Protocol.send_notice(nick_from=dnickname, nick_to=u.nickname, msg="This is a notice to the sender ...")
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=u.nickname)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="This is a notice to the sender ...")
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=fromuser)
if not fromchannel is None:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=fromchannel)
if c is not None:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=c.name)
# How to update your module configuration
self.__update_configuration('param_exemple2', 7)
self.update_configuration('param_exemple2', 7)
self.update_configuration('param_exemple1', 'my_value')
# Log if you want the result
self.Logs.debug(f"Test logs ready")

View File

@@ -1,95 +1,37 @@
"""
File : mod_votekick.py
Version : 1.0.0
Version : 1.0.2
Description : Manages votekick sessions for multiple channels.
Handles activation, ongoing vote checks, and cleanup.
Author : adator
Created : 2025-08-16
Last Updated: 2025-08-16
Last Updated: 2025-11-01
-----------------------------------------
"""
from dataclasses import dataclass
import re
from core.classes.interfaces.imodule import IModule
import mods.votekick.schemas as schemas
import mods.votekick.utils as utils
from mods.votekick.votekick_manager import VotekickManager
import mods.votekick.threads as thds
from typing import TYPE_CHECKING, Any, Optional
from typing import Any, Optional
if TYPE_CHECKING:
from core.irc import Irc
class Votekick(IModule):
@dataclass
class ModConfModel(schemas.VoteChannelModel):
...
class Votekick:
MOD_HEADER: dict[str, str] = {
'name':'votekick',
'version':'1.0.2',
'description':'Channel Democraty',
'author':'Defender Team',
'core_version':'Defender-6'
}
def __init__(self, uplink: 'Irc') -> None:
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module
self.Irc = uplink
# Add Loader Object to the module (Mandatory)
self.Loader = uplink.Loader
# Add server protocol Object to the module (Mandatory)
self.Protocol = uplink.Protocol
# Add Global Configuration to the module
self.Config = uplink.Config
# Add Base object to the module
self.Base = uplink.Base
# Add logs object to the module
self.Logs = uplink.Logs
# Add User object to the module
self.User = uplink.User
# Add Channel object to the module
self.Channel = uplink.Channel
# Add Utils.
self.Utils = uplink.Utils
# Add Utils module
self.ModUtils = utils
# Add Schemas module
self.Schemas = schemas
# Add Threads module
self.Threads = thds
# Add VoteKick Manager
self.VoteKickManager = VotekickManager(self)
metadata = uplink.Loader.Settings.get_cache('VOTEKICK')
if metadata is not None:
self.VoteKickManager.VOTE_CHANNEL_DB = metadata
# self.VOTE_CHANNEL_DB = metadata
# Créer les nouvelles commandes du module
self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module')
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'-- Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Add admin object to retrieve admin users
self.Admin = self.Irc.Admin
self.__create_tables()
self.ModUtils.join_saved_channels(self)
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.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
@@ -115,10 +57,37 @@ class Votekick:
self.Base.db_execute_query(table_vote)
return None
def load(self) -> None:
self.ModConfig = self.ModConfModel()
# Add VoteKick Manager
self.VoteKickManager = VotekickManager(self)
# Add Utils module
self.ModUtils = utils
# Add Schemas module
self.Schemas = schemas
# Add Threads module
self.Threads = thds
self.ModUtils.join_saved_channels(self)
metadata = self.Settings.get_cache('VOTEKICK')
if metadata is not None:
self.VoteKickManager.VOTE_CHANNEL_DB = metadata
# Créer les nouvelles commandes du module
self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module')
def unload(self) -> None:
try:
# Cache the local DB with current votes.
self.Loader.Settings.set_cache('VOTEKICK', self.VoteKickManager.VOTE_CHANNEL_DB)
if self.VoteKickManager.VOTE_CHANNEL_DB:
self.Settings.set_cache('VOTEKICK', self.VoteKickManager.VOTE_CHANNEL_DB)
for chan in self.VoteKickManager.VOTE_CHANNEL_DB:
self.Protocol.send_part_chan(uidornickname=self.Config.SERVICE_ID, channel=chan.channel_name)

View File

@@ -11,7 +11,7 @@ class VotekickManager:
def __init__(self, uplink: 'Votekick'):
self.uplink = uplink
self.Logs = uplink.Logs
self.Utils = uplink.Utils
self.Utils = uplink.MainUtils
def activate_new_channel(self, channel_name: str) -> bool:
"""Activate a new channel in the votekick systeme

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.3",
"requests": "2.32.3",
"psutil": "6.0.0",
"unrealircd_rpc_py": "2.0.5",
"sqlalchemy": "2.0.35",
"faker": "30.1.0",
"pyyaml": "6.0.2"
}
"requests": "2.32.5",
"psutil": "7.1.2",
"unrealircd_rpc_py": "3.0.1",
"sqlalchemy": "2.0.44",
"faker": "37.12.0",
"pyyaml": "6.0.3"
}