mirror of
https://github.com/iio612/DEFENDER.git
synced 2026-02-13 11:14:23 +00:00
first commit
This commit is contained in:
65
README_1.md
Normal file
65
README_1.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# IRC-DEFENDER
|
||||||
|
Defender est un service IRC basé sur la sécurité des réseaux IRC ( UnrealIRCD )
|
||||||
|
Il permet d'ajouter une sécurité supplémentaire pour vérifier les users connectés au réseau
|
||||||
|
en demandant aux user un code de validation.
|
||||||
|
|
||||||
|
Pré-requis :
|
||||||
|
|
||||||
|
- Python version >= 3.10
|
||||||
|
- Pip de python installé sur la machine
|
||||||
|
- Python librairies psutil & sqlalchemy & requests
|
||||||
|
- IRC Serveur Version >= UnrealIRCd-6.1.2.2
|
||||||
|
|
||||||
|
Lancement de Defender :
|
||||||
|
|
||||||
|
- Installer les librairies python : psutil & sqlalchemy & requests
|
||||||
|
- pip3 install psutil sqlalchemy requests ou pip install psutil sqlalchemy requests
|
||||||
|
- Ne pas lancer Defender en tant que root
|
||||||
|
- Créer plutot un service qui lancera Defender en tant qu'utilisateur non root
|
||||||
|
- Un fichier PID sera crée
|
||||||
|
|
||||||
|
# TO DO LIST
|
||||||
|
|
||||||
|
- Optimiser le systeme de réputation:
|
||||||
|
- lorsque les users ce connectent, Ils entrent dans un salon puis une fraction de seconde le service les bans
|
||||||
|
- Déplacer les deux variables RESTART et INIT de la configuration a Irc
|
||||||
|
|
||||||
|
|
||||||
|
# VERSION 1
|
||||||
|
[28.12.2023]
|
||||||
|
- Changement de méthode pour récuperer la version actuelle de python
|
||||||
|
- Ajout de la réponse a une PING de la part d'un utilisateur
|
||||||
|
- Installation automatique des packages sqlalchemy, requests et psutil
|
||||||
|
|
||||||
|
# BUG FIX
|
||||||
|
|
||||||
|
[29.12.2023]
|
||||||
|
- Correction des messages de receptions trop longs > 4070 caractéres;
|
||||||
|
- la méthode boucle et incrémente la réponse tant que le nombre de caractére reçu est supérieur a 4072
|
||||||
|
- Rajout du protocol MTAGS a la connexion du service
|
||||||
|
- Impact majeur dans la lecture des messages reçu du serveur ( PRIVMSG, SLOGS, UID, QUIT, NICK, PONG, SJOIN)
|
||||||
|
|
||||||
|
# ALREADY IMPLEMENTED
|
||||||
|
|
||||||
|
- Connexion en tant que service
|
||||||
|
- Gestion des messages reçus/envoyés par le serveur
|
||||||
|
- Gestion des caractéres spéciaux
|
||||||
|
- Gestion des logs (salon, fichiers et console)
|
||||||
|
- Mode debug : gestion des logs coté console
|
||||||
|
- Création du systeme de gestion de commandes
|
||||||
|
- Defender reconnait les commandes qui commence par le suffix définit dans la configuration
|
||||||
|
- Defender reconnait aussi reconnaitre les commandes qui viennent de /msg Defender [commande]
|
||||||
|
- Identifications
|
||||||
|
- Systéme d'identification [OK]
|
||||||
|
- Systéme de changement d'information [OK]
|
||||||
|
- Suppression d'un admin
|
||||||
|
- Systéme de groupe d'accés [OK]
|
||||||
|
|
||||||
|
Reputation security
|
||||||
|
- Activation ou désaction du systéme --> OK | .reputation ON/off
|
||||||
|
- Le user sera en mesure de changer la limite de la réputation --> OK | .reputation set limit 120
|
||||||
|
- Defender devra envoyer l'utilisateur dans un salon définit dans la configuration --> OK
|
||||||
|
- Defender bannira l'utilisateur de la totalité des salons, il le bannira aussi lorsqu'il souhaitera accéder a de nouveau salon --> OK
|
||||||
|
- Defender devra envoyer un message du type "Merci de taper cette comande /msg {nomdudefender} code {un code générer aléatoirement} --> OK
|
||||||
|
- Defender devra reconnaitre le code --> OK
|
||||||
|
- Defender devra liberer l'utilisateur et l'envoyer vers un salon définit dans la configuration --> OK
|
||||||
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
413
core/base.py
Normal file
413
core/base.py
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
import time, threading, os, sys, random, socket, hashlib
|
||||||
|
from platform import python_version
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import create_engine, Engine, Connection, CursorResult
|
||||||
|
from sqlalchemy.sql import text
|
||||||
|
from core.configuration import Config
|
||||||
|
|
||||||
|
class Base:
|
||||||
|
|
||||||
|
CORE_DB_PATH = 'core' + os.sep + 'db' + os.sep # Le dossier bases de données core
|
||||||
|
MODS_DB_PATH = 'mods' + os.sep + 'db' + os.sep # Le dossier bases de données des modules
|
||||||
|
PYTHON_MIN_VERSION = '3.10' # Version min de python
|
||||||
|
DB_SCHEMA:list[str] = {
|
||||||
|
'admins': 'sys_admins',
|
||||||
|
'commandes': 'sys_commandes',
|
||||||
|
'logs': 'sys_logs',
|
||||||
|
'modules': 'sys_modules'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, Config: Config) -> None:
|
||||||
|
|
||||||
|
self.Config = Config # Assigner l'objet de configuration
|
||||||
|
|
||||||
|
# Tester si la version de python est correcte
|
||||||
|
if not self.isRightPythonVersion():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self.running_timers:list[threading.Timer] = [] # Liste des timers en cours
|
||||||
|
self.running_threads:list[threading.Thread] = [] # Liste des threads en cours
|
||||||
|
self.running_sockets: list[socket.socket] = [] # Les sockets ouvert
|
||||||
|
self.periodic_func:dict[object] = {} # Liste des fonctions en attentes
|
||||||
|
|
||||||
|
self.lock = threading.RLock() # Création du lock
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
self.db_create_first_admin()
|
||||||
|
|
||||||
|
def isRightPythonVersion(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
|
||||||
|
"""
|
||||||
|
python_required_version = self.PYTHON_MIN_VERSION.split('.')
|
||||||
|
python_current_version = python_version().split('.')
|
||||||
|
|
||||||
|
if int(python_current_version[0]) < int(python_required_version[0]):
|
||||||
|
print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##")
|
||||||
|
return False
|
||||||
|
elif int(python_current_version[1]) < int(python_required_version[1]):
|
||||||
|
print(f"### Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ###")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# print(f"===> Version of python : {python_version()} ==> OK")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_unixtime(self)->int:
|
||||||
|
"""
|
||||||
|
Cette fonction retourne un UNIXTIME de type 12365456
|
||||||
|
Return: Current time in seconds since the Epoch (int)
|
||||||
|
"""
|
||||||
|
unixtime = int( time.time() )
|
||||||
|
return unixtime
|
||||||
|
|
||||||
|
def get_datetime(self)->str:
|
||||||
|
"""
|
||||||
|
Retourne une date au format string (24-12-2023 20:50:59)
|
||||||
|
"""
|
||||||
|
currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S')
|
||||||
|
return currentdate
|
||||||
|
|
||||||
|
def create_log(self, log_message: str) -> None:
|
||||||
|
"""Enregiste les logs
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): Le message a enregistrer
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None: Aucun retour
|
||||||
|
"""
|
||||||
|
sql_insert = f"INSERT INTO {self.DB_SCHEMA['logs']} (datetime, server_msg) VALUES (:datetime, :server_msg)"
|
||||||
|
mes_donnees = {'datetime': str(self.get_datetime()),'server_msg': f'{log_message}'}
|
||||||
|
self.db_execute_query(sql_insert, mes_donnees)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def log_cmd(self, user_cmd:str, cmd:str) -> None:
|
||||||
|
"""Enregistre les commandes envoyées par les utilisateurs
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmd (str): la commande a enregistrer
|
||||||
|
"""
|
||||||
|
insert_cmd_query = f"INSERT INTO {self.DB_SCHEMA['commandes']} (datetime, user, commande) VALUES (:datetime, :user, :commande)"
|
||||||
|
mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'commande': cmd}
|
||||||
|
self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def db_isModuleExist(self, module_name:str) -> bool:
|
||||||
|
"""Teste si un module existe déja dans la base de données
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module_name (str): le non du module a chercher dans la base de données
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True si le module existe déja dans la base de données sinon False
|
||||||
|
"""
|
||||||
|
query = f"SELECT id FROM {self.DB_SCHEMA['modules']} WHERE module = :module"
|
||||||
|
mes_donnes = {'module': module_name}
|
||||||
|
results = self.db_execute_query(query, mes_donnes)
|
||||||
|
|
||||||
|
if results.fetchall():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def db_record_module(self, user_cmd:str, module_name:str) -> None:
|
||||||
|
"""Enregistre les modules dans la base de données
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmd (str): le module a enregistrer
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.db_isModuleExist(module_name):
|
||||||
|
self.__debug(f"Le module {module_name} n'existe pas alors ont le créer")
|
||||||
|
insert_cmd_query = f"INSERT INTO {self.DB_SCHEMA['modules']} (datetime, user, module) VALUES (:datetime, :user, :module)"
|
||||||
|
mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module': module_name}
|
||||||
|
self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||||
|
# self.db_close_session(self.session)
|
||||||
|
else:
|
||||||
|
self.__debug(f"Le module {module_name} existe déja dans la base de données")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def db_delete_module(self, module_name:str) -> None:
|
||||||
|
"""Supprime les modules de la base de données
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmd (str): le module a enregistrer
|
||||||
|
"""
|
||||||
|
insert_cmd_query = f"DELETE FROM {self.DB_SCHEMA['modules']} WHERE module = :module"
|
||||||
|
mes_donnees = {'module': module_name}
|
||||||
|
self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def db_create_first_admin(self) -> None:
|
||||||
|
|
||||||
|
user = self.db_execute_query(f"SELECT id FROM {self.DB_SCHEMA['admins']}")
|
||||||
|
if not user.fetchall():
|
||||||
|
admin = self.Config.OWNER
|
||||||
|
password = self.crypt_password(self.Config.PASSWORD)
|
||||||
|
|
||||||
|
mes_donnees = {'createdOn': self.get_datetime(), 'user': admin, 'password': password, 'hostname': '*', 'vhost': '*', 'level': 5}
|
||||||
|
self.db_execute_query(f"""
|
||||||
|
INSERT INTO {self.DB_SCHEMA['admins']}
|
||||||
|
(createdOn, user, password, hostname, vhost, level)
|
||||||
|
VALUES
|
||||||
|
(:createdOn, :user, :password, :hostname, :vhost, :level)"""
|
||||||
|
, mes_donnees)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_timer(self, time_to_wait: float, func: object, func_args: tuple = ()) -> None:
|
||||||
|
|
||||||
|
try:
|
||||||
|
t = threading.Timer(interval=time_to_wait, function=func, args=func_args)
|
||||||
|
t.setName(func.__name__)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
self.running_timers.append(t)
|
||||||
|
|
||||||
|
self.__debug(f"Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}")
|
||||||
|
|
||||||
|
except AssertionError as ae:
|
||||||
|
self.__debug(f'Assertion Error -> {ae}')
|
||||||
|
|
||||||
|
def create_thread(self, func:object, func_args: tuple = ()) -> None:
|
||||||
|
try:
|
||||||
|
func_name = func.__name__
|
||||||
|
# if func_name in self.running_threads:
|
||||||
|
# print(f"HeartBeat is running")
|
||||||
|
# return None
|
||||||
|
|
||||||
|
th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=True)
|
||||||
|
th.start()
|
||||||
|
|
||||||
|
self.running_threads.append(th)
|
||||||
|
self.__debug(f"Thread ID : {str(th.ident)} | Thread name : {th.getName()} | Running Threads : {len(threading.enumerate())}")
|
||||||
|
|
||||||
|
except AssertionError as ae:
|
||||||
|
self.__debug(f'Assertion Error -> {ae}')
|
||||||
|
|
||||||
|
def garbage_collector_timer(self) -> None:
|
||||||
|
"""Methode qui supprime les timers qui ont finis leurs job
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.__debug(f"=======> Checking for Timers to stop")
|
||||||
|
# print(f"{self.running_timers}")
|
||||||
|
for timer in self.running_timers:
|
||||||
|
if not timer.is_alive():
|
||||||
|
timer.cancel()
|
||||||
|
self.running_timers.remove(timer)
|
||||||
|
self.__debug(f"Timer {str(timer)} removed")
|
||||||
|
else:
|
||||||
|
self.__debug(f"===> Timer {str(timer)} Still running ...")
|
||||||
|
|
||||||
|
except AssertionError as ae:
|
||||||
|
print(f'Assertion Error -> {ae}')
|
||||||
|
|
||||||
|
def garbage_collector_thread(self) -> None:
|
||||||
|
"""Methode qui supprime les threads qui ont finis leurs job
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.__debug(f"=======> Checking for Threads to stop")
|
||||||
|
for thread in self.running_threads:
|
||||||
|
if thread.getName() != 'heartbeat':
|
||||||
|
if not thread.is_alive():
|
||||||
|
self.running_threads.remove(thread)
|
||||||
|
self.__debug(f"Thread {str(thread.getName())} {str(thread.native_id)} removed")
|
||||||
|
|
||||||
|
# print(threading.enumerate())
|
||||||
|
except AssertionError as ae:
|
||||||
|
self.__debug(f'Assertion Error -> {ae}')
|
||||||
|
|
||||||
|
def garbage_collector_sockets(self) -> None:
|
||||||
|
|
||||||
|
self.__debug(f"=======> Checking for Sockets to stop")
|
||||||
|
for soc in self.running_sockets:
|
||||||
|
while soc.fileno() != -1:
|
||||||
|
self.__debug(soc.fileno())
|
||||||
|
soc.close()
|
||||||
|
|
||||||
|
soc.close()
|
||||||
|
self.running_sockets.remove(soc)
|
||||||
|
self.__debug(f"Socket ==> closed {str(soc.fileno())}")
|
||||||
|
|
||||||
|
def shutdown(self) -> None:
|
||||||
|
"""Methode qui va préparer l'arrêt complêt du service
|
||||||
|
"""
|
||||||
|
# Nettoyage des timers
|
||||||
|
print(f"=======> Checking for Timers to stop")
|
||||||
|
for timer in self.running_timers:
|
||||||
|
while timer.is_alive():
|
||||||
|
print(f"> waiting for {timer.getName()} to close")
|
||||||
|
timer.cancel()
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.running_timers.remove(timer)
|
||||||
|
print(f"> Cancelling {timer.getName()} {timer.native_id}")
|
||||||
|
|
||||||
|
print(f"=======> Checking for Threads to stop")
|
||||||
|
for thread in self.running_threads:
|
||||||
|
if thread.getName() == 'heartbeat' and thread.is_alive():
|
||||||
|
self.execute_periodic_action()
|
||||||
|
print(f"> Running the last periodic action")
|
||||||
|
self.running_threads.remove(thread)
|
||||||
|
print(f"> Cancelling {thread.getName()} {thread.native_id}")
|
||||||
|
|
||||||
|
print(f"=======> Checking for Sockets to stop")
|
||||||
|
for soc in self.running_sockets:
|
||||||
|
soc.close()
|
||||||
|
while soc.fileno() != -1:
|
||||||
|
soc.close()
|
||||||
|
|
||||||
|
self.running_sockets.remove(soc)
|
||||||
|
print(f"> Socket ==> closed {str(soc.fileno())}")
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def db_init(self) -> tuple[Engine, Connection]:
|
||||||
|
|
||||||
|
db_directory = self.Config.DEFENDER_DB_PATH
|
||||||
|
full_path_db = self.Config.DEFENDER_DB_PATH + self.Config.DEFENDER_DB_NAME
|
||||||
|
|
||||||
|
if not os.path.exists(db_directory):
|
||||||
|
os.makedirs(db_directory)
|
||||||
|
|
||||||
|
engine = create_engine(f'sqlite:///{full_path_db}.db', echo=False)
|
||||||
|
cursor = engine.connect()
|
||||||
|
|
||||||
|
return engine, cursor
|
||||||
|
|
||||||
|
def __create_db(self) -> None:
|
||||||
|
|
||||||
|
table_logs = f'''CREATE TABLE IF NOT EXISTS {self.DB_SCHEMA['logs']} (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
datetime TEXT,
|
||||||
|
server_msg TEXT
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
table_cmds = f'''CREATE TABLE IF NOT EXISTS {self.DB_SCHEMA['commandes']} (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
datetime TEXT,
|
||||||
|
user TEXT,
|
||||||
|
commande TEXT
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
table_modules = f'''CREATE TABLE IF NOT EXISTS {self.DB_SCHEMA['modules']} (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
datetime TEXT,
|
||||||
|
user TEXT,
|
||||||
|
module TEXT
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
table_admins = f'''CREATE TABLE IF NOT EXISTS {self.DB_SCHEMA['admins']} (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
createdOn TEXT,
|
||||||
|
user TEXT,
|
||||||
|
hostname TEXT,
|
||||||
|
vhost TEXT,
|
||||||
|
password TEXT,
|
||||||
|
level INTEGER
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.db_execute_query(table_logs)
|
||||||
|
self.db_execute_query(table_cmds)
|
||||||
|
self.db_execute_query(table_modules)
|
||||||
|
self.db_execute_query(table_admins)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def db_execute_query(self, query:str, params:dict = {}) -> CursorResult:
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
insert_query = text(query)
|
||||||
|
if not params:
|
||||||
|
response = self.cursor.execute(insert_query)
|
||||||
|
else:
|
||||||
|
response = self.cursor.execute(insert_query, params)
|
||||||
|
|
||||||
|
self.cursor.commit()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def db_close(self) -> None:
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.close()
|
||||||
|
except AttributeError as ae:
|
||||||
|
self.__debug(f"Attribute Error : {ae}")
|
||||||
|
|
||||||
|
def crypt_password(self, password:str) -> str:
|
||||||
|
"""Retourne un mot de passe chiffré en MD5
|
||||||
|
|
||||||
|
Args:
|
||||||
|
password (str): Le password en clair
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Le password en MD5
|
||||||
|
"""
|
||||||
|
md5_password = hashlib.md5(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
return md5_password
|
||||||
|
|
||||||
|
def int_if_possible(self, value):
|
||||||
|
"""Convertit la valeur reçue en entier, si possible.
|
||||||
|
Sinon elle retourne la valeur initiale.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (any): la valeur à convertir
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
any: Retour un entier, si possible. Sinon la valeur initiale.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = int(value)
|
||||||
|
return response
|
||||||
|
except ValueError:
|
||||||
|
return value
|
||||||
|
except TypeError:
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_random(self, lenght:int) -> str:
|
||||||
|
"""
|
||||||
|
Retourn une chaîne aléatoire en fonction de la longueur spécifiée.
|
||||||
|
"""
|
||||||
|
caracteres = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
randomize = ''.join(random.choice(caracteres) for _ in range(lenght))
|
||||||
|
|
||||||
|
return randomize
|
||||||
|
|
||||||
|
def execute_periodic_action(self) -> None:
|
||||||
|
|
||||||
|
if not self.periodic_func:
|
||||||
|
# Run Garbage Collector Timer
|
||||||
|
self.garbage_collector_timer()
|
||||||
|
self.garbage_collector_thread()
|
||||||
|
self.garbage_collector_sockets()
|
||||||
|
return None
|
||||||
|
|
||||||
|
for key, value in self.periodic_func.items():
|
||||||
|
obj = value['object']
|
||||||
|
method_name = value['method_name']
|
||||||
|
param = value['param']
|
||||||
|
f = getattr(obj, method_name, None)
|
||||||
|
f(*param)
|
||||||
|
|
||||||
|
# Vider le dictionnaire de fonction
|
||||||
|
self.periodic_func.clear()
|
||||||
|
|
||||||
|
def __debug(self, debug_msg:str) -> None:
|
||||||
|
|
||||||
|
if self.Config.DEBUG == 1:
|
||||||
|
print(f"[{self.get_datetime()}] - {debug_msg}")
|
||||||
|
|
||||||
|
return None
|
||||||
53
core/exemple_configuration.py
Normal file
53
core/exemple_configuration.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import os
|
||||||
|
##########################################
|
||||||
|
# CONFIGURATION FILE : #
|
||||||
|
# Rename file to : configuration.py #
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
|
||||||
|
DEFENDER_VERSION = '1.1.0' # MAJOR.MINOR.BATCH
|
||||||
|
DEFENDER_DB_PATH = 'db' + os.sep # Séparateur en fonction de l'OS
|
||||||
|
DEFENDER_DB_NAME = 'defender' # Le nom de la base de données principale
|
||||||
|
SERVICE_NAME = 'defender' # Le nom du service
|
||||||
|
|
||||||
|
SERVEUR_IP = '8.8.8.8' # IP ou host du serveur à rejoindre
|
||||||
|
SERVEUR_HOSTNAME = 'your hostname' # Le hostname du serveur IRC
|
||||||
|
SERVEUR_LINK = 'your link' # Host attendu par votre IRCd (ex. dans votre link block pour Unrealircd)
|
||||||
|
SERVEUR_PORT = 6666 # Port du link
|
||||||
|
SERVEUR_PASSWORD = 'your link password' # Mot de passe du link (Privilégiez argon2 sur Unrealircd)
|
||||||
|
SERVEUR_ID = '002' # SID (identification) du bot en tant que Services
|
||||||
|
|
||||||
|
SERVICE_NICKNAME = 'BotName' # Nick du bot sur IRC
|
||||||
|
SERVICE_REALNAME = 'BotRealname' # Realname du bot
|
||||||
|
SERVICE_USERNAME = 'BotIdent' # Ident du bot
|
||||||
|
SERVICE_HOST = 'your service host' # Host du bot
|
||||||
|
SERVICE_INFO = 'Network IRC Service' # swhois du bot
|
||||||
|
SERVICE_CHANLOG = '#services' # Salon des logs et autres messages issus du bot
|
||||||
|
SERVICE_SMODES = '+ioqBS' # Mode du service
|
||||||
|
SERVICE_CMODES = 'ntsO' # Mode du salon (#ChanLog) que le bot appliquera à son entrée
|
||||||
|
SERVICE_UMODES = 'o' # Mode que le bot pourra se donner à sa connexion au salon chanlog
|
||||||
|
SERVICE_PREFIX = '.' # Prefix pour envoyer les commandes au bot
|
||||||
|
SERVICE_ID = SERVEUR_ID + 'AAAAAB' # L'identifiant du service
|
||||||
|
|
||||||
|
OWNER = 'admin' # Identifiant du compte admin
|
||||||
|
PASSWORD = 'password' # Mot de passe du compte admin
|
||||||
|
|
||||||
|
SALON_JAIL = '#JAIL' # Salon pot de miel
|
||||||
|
SALON_JAIL_MODES = 'sS' # Mode du salon pot de miel
|
||||||
|
SALON_LIBERER = '#welcome' # Le salon ou sera envoyé l'utilisateur clean
|
||||||
|
|
||||||
|
PORTS_TO_SCAN = [3028, 8080, 1080, 1085, 4145, 9050] # Liste des ports a scanné pour une detection de proxy
|
||||||
|
|
||||||
|
DEBUG = 0 # Afficher l'ensemble des messages du serveurs dans la console
|
||||||
|
|
||||||
|
CONFIG_COLOR = {
|
||||||
|
'blanche': '\x0300', # Couleur blanche
|
||||||
|
'noire': '\x0301', # Couleur noire
|
||||||
|
'bleue': '\x0302', # Couleur Bleue
|
||||||
|
'verte': '\x0303', # Couleur Verte
|
||||||
|
'rouge': '\x0304', # Couleur rouge
|
||||||
|
'jaune': '\x0306', # Couleur jaune
|
||||||
|
'gras': '\x02', # Gras
|
||||||
|
'nogc': '\x02\x03' # Retirer gras et couleur
|
||||||
|
}
|
||||||
66
core/installation.py
Normal file
66
core/installation.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from importlib.util import find_spec
|
||||||
|
from subprocess import check_call, run
|
||||||
|
from platform import python_version
|
||||||
|
from sys import exit
|
||||||
|
|
||||||
|
class Install:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.PYTHON_MIN_VERSION = '3.10'
|
||||||
|
self.module_to_install = ['sqlalchemy','psutil','requests']
|
||||||
|
|
||||||
|
if not self.checkPythonVersion():
|
||||||
|
# Tester si c'est la bonne version de python
|
||||||
|
exit("Python Version Error")
|
||||||
|
else:
|
||||||
|
# Sinon tester les dependances python et les installer avec pip
|
||||||
|
self.checkDependencies()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def checkPythonVersion(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
|
||||||
|
"""
|
||||||
|
python_required_version = self.PYTHON_MIN_VERSION.split('.')
|
||||||
|
python_current_version = python_version().split('.')
|
||||||
|
|
||||||
|
if int(python_current_version[0]) < int(python_required_version[0]):
|
||||||
|
print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##")
|
||||||
|
return False
|
||||||
|
elif int(python_current_version[1]) < int(python_required_version[1]):
|
||||||
|
print(f"### Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ###")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"===> Version of python : {python_version()} ==> OK")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def checkDependencies(self) -> None:
|
||||||
|
"""Check python dependencies
|
||||||
|
"""
|
||||||
|
do_install = False
|
||||||
|
|
||||||
|
for module in self.module_to_install:
|
||||||
|
if find_spec(module) is None:
|
||||||
|
do_install = True
|
||||||
|
|
||||||
|
if not do_install:
|
||||||
|
return None
|
||||||
|
|
||||||
|
print("===> Verifier si pip est a jour")
|
||||||
|
run(['python', '-m', 'pip', 'install', '--upgrade', 'pip'])
|
||||||
|
|
||||||
|
if find_spec('greenlet') is None:
|
||||||
|
check_call(['pip','install', '--only-binary', ':all:', 'greenlet'])
|
||||||
|
print('====> Module Greenlet installé')
|
||||||
|
|
||||||
|
for module in self.module_to_install:
|
||||||
|
if find_spec(module) is None:
|
||||||
|
print("### Trying to install missing python packages ###")
|
||||||
|
check_call(['pip','install', module])
|
||||||
|
print(f"====> Module {module} installé")
|
||||||
|
else:
|
||||||
|
print(f"==> {module} already installed")
|
||||||
1145
core/irc.py
Normal file
1145
core/irc.py
Normal file
File diff suppressed because it is too large
Load Diff
26
main.py
Normal file
26
main.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from core import installation
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# @Version : 1 #
|
||||||
|
# Requierements : #
|
||||||
|
# Python3.10 or higher #
|
||||||
|
# SQLAlchemy, requests, psutil #
|
||||||
|
# UnrealIRCD 6.2.2 or higher #
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
#########################
|
||||||
|
# LANCEMENT DE DEFENDER #
|
||||||
|
#########################
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
installation.Install()
|
||||||
|
|
||||||
|
from core.irc import Irc
|
||||||
|
ircInstance = Irc()
|
||||||
|
ircInstance.init_irc(ircInstance)
|
||||||
|
|
||||||
|
except AssertionError as ae:
|
||||||
|
print(f'Assertion Error -> {ae}')
|
||||||
|
except KeyboardInterrupt as k:
|
||||||
|
ircInstance.Base.execute_periodic_action()
|
||||||
0
mods/__init__.py
Normal file
0
mods/__init__.py
Normal file
1245
mods/mod_defender.py
Normal file
1245
mods/mod_defender.py
Normal file
File diff suppressed because it is too large
Load Diff
83
mods/mod_test.py
Normal file
83
mods/mod_test.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import threading
|
||||||
|
from core.irc import Irc
|
||||||
|
|
||||||
|
# Le module crée devra réspecter quelques conditions
|
||||||
|
# 1. Importer le module de configuration
|
||||||
|
# 2. Le nom de class devra toujours s'appeler comme le module exemple => nom de class Dktmb | nom du module mod_dktmb
|
||||||
|
# 3. la fonction __init__ devra toujours avoir les parametres suivant (self, irc:object)
|
||||||
|
# 1 . Créer la variable irc dans le module
|
||||||
|
# 2 . Récuperer la configuration dans une variable
|
||||||
|
# 3 . Définir et enregistrer les nouvelles commandes
|
||||||
|
# 4. une fonction _hcmds(self, user:str, cmd: list) devra toujours etre crée.
|
||||||
|
|
||||||
|
class Test():
|
||||||
|
|
||||||
|
def __init__(self, ircInstance:Irc) -> None:
|
||||||
|
print(f'Module {self.__class__.__name__} loaded ...')
|
||||||
|
|
||||||
|
self.irc = ircInstance # Ajouter l'object mod_irc a la classe
|
||||||
|
|
||||||
|
self.config = ircInstance.Config # Ajouter la configuration a la classe
|
||||||
|
|
||||||
|
# Créer les nouvelles commandes du module
|
||||||
|
self.commands = ['test']
|
||||||
|
|
||||||
|
self.__set_commands(self.commands) # Enrigstrer les nouvelles commandes dans le code
|
||||||
|
|
||||||
|
self.core = ircInstance.Base # Instance du module Base
|
||||||
|
|
||||||
|
self.session = '' # Instancier une session pour la base de données
|
||||||
|
self.__create_db('mod_test') # Créer la base de données si necessaire
|
||||||
|
|
||||||
|
def __set_commands(self, commands:list) -> None:
|
||||||
|
"""Rajoute les commandes du module au programme principal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
commands (list): Liste des commandes du module
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None: Aucun retour attendu
|
||||||
|
"""
|
||||||
|
for command in commands:
|
||||||
|
self.irc.commands.append(command)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __create_db(self, db_name:str) -> 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
|
||||||
|
"""
|
||||||
|
db_directory = self.core.MODS_DB_PATH
|
||||||
|
|
||||||
|
self.session = self.core.db_init(db_directory, db_name)
|
||||||
|
|
||||||
|
table_logs = '''CREATE TABLE IF NOT EXISTS logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
datetime TEXT,
|
||||||
|
server_msg TEXT
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.core.db_execute_query(self.session, table_logs)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _hcmds(self, user:str, cmd: list) -> None:
|
||||||
|
|
||||||
|
command = cmd[0].lower()
|
||||||
|
|
||||||
|
match command:
|
||||||
|
|
||||||
|
case 'test':
|
||||||
|
try:
|
||||||
|
user_action = cmd[1]
|
||||||
|
self.irc.send2socket(f'PRIVMSG #webmail Je vais voicer {user}')
|
||||||
|
self.irc.send2socket(f'MODE #webmail +v {user_action}')
|
||||||
|
self.core.create_log(f"MODE +v sur {user_action}")
|
||||||
|
except KeyError as ke:
|
||||||
|
self.core.create_log(f"Key Error : {ke}")
|
||||||
|
|
||||||
Reference in New Issue
Block a user