13 Commits

Author SHA1 Message Date
adator
f0853e3afb Merge pull request #21 from adator85/dev
New Installation file created for unix system
2024-08-22 01:02:00 +02:00
adator
88b9b056ca New Installation file created for unix system 2024-08-22 01:01:21 +02:00
adator
6dade09257 Merge pull request #20 from adator85/dev
README Update
2024-08-21 00:50:31 +02:00
adator
d7fab2d701 README Update
Version Update
base.py:
    - Adding timeout variable to github connexion
    - Adding get_all_module method to retrieve all modules in mods/ folder
irc.py:
    - Adapt show_module command
mod_defender.py:
    - Update operator command and use only normal command (owner, deowner, op, deop, halfop, dehalfop, voice, devoice, kick, kickban, ban)
    - Channel variable is coming now from the command but also from the system
2024-08-21 00:43:20 +02:00
adator
9533b010b2 Merge pull request #19 from adator85/dev
V5.0.4 - Delete a user when a user has been kicked
2024-08-20 02:24:37 +02:00
adator
dbfc04a936 V5.0.4 - Delete a user when a user has been kicked 2024-08-20 02:24:11 +02:00
adator
824db73590 Merge pull request #18 from adator85/dev
Delete channel mode information
2024-08-20 02:14:31 +02:00
adator
bfb449f804 Delete channel mode information 2024-08-20 02:13:54 +02:00
adator
96bf4b6f80 Merge pull request #17 from adator85/dev
Fix channel update
2024-08-20 02:08:09 +02:00
adator
8c772f5882 Fix channel update 2024-08-20 02:07:21 +02:00
adator
922336363e Merge pull request #16 from adator85/dev
Dev
2024-08-20 01:56:04 +02:00
adator
e5ceada997 Merge pull request #15 from adator85/mac
Mac
2024-08-20 01:51:43 +02:00
adator
e4781614f4 V5.0.1 2024-08-20 01:50:08 +02:00
11 changed files with 988 additions and 164 deletions

1
.gitignore vendored
View File

@@ -3,4 +3,5 @@ db/
logs/ logs/
__pycache__/ __pycache__/
configuration.json configuration.json
install.log
test.py test.py

140
README.md
View File

@@ -2,70 +2,104 @@
Defender est un service IRC basé sur la sécurité des réseaux IRC ( UnrealIRCD ) 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 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. en demandant aux user un code de validation.
Il permet aux opérateurs de gérer efficacement un canal, tout en offrant aux utilisateurs des outils d'interaction et de décision collective.
Pré-requis : # Fonctionnalités principales
Commandes opérateurs complètes:
Kick: Expulser un utilisateur du canal.
Ban: Interdire définitivement l'accès au canal.
Unban: Lever une interdiction.
Op/Deop: Attribuer ou retirer les droits d'opérateur.
Halfop/Dehalfop: Attribuer ou retirer les droits
Voice/Devoice: Attribuer ou retirer les droits de voix.
- Python version >= 3.10 Système de quarantaine:
- Pip de python installé sur la machine Mise en quarantaine: Isoler temporairement un utilisateur dans un canal privé.
- Python librairies psutil & sqlalchemy & requests Libération: Permettre à un utilisateur de quitter la quarantaine en entrant un code spécifique.
- IRC Serveur Version >= UnrealIRCd-6.1.2.2
Lancement de Defender : Système de vote:
Kick: Les utilisateurs peuvent voter pour expulser un membre du canal.
Autres actions: Possibilité d'étendre le système de vote à d'autres actions (ban, etc.).
- Installer les librairies python : psutil & sqlalchemy & requests # Installation automatique sur une machine Debian/Ubuntu
- 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 Prérequis:
- Système d'exploitation Linux (Windows non supporté)
- Droits d'administrateur (root) pour l'exécution du script
- Python version 3.10 ou supérieure
- Optimiser le systeme de réputation: Bash
- lorsque les users ce connectent, Ils entrent dans un salon puis une fraction de seconde le service les bans $ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git
- Renommer le fichier exemple_configuration.json en configuration.json
- Configurer le fichier configuration.json
$ sudo python3 install.py
# VERSION 1 Si votre configuration est bonne, votre service est censé etre connecté a votre réseau IRC
[02.01.2024] # Installation manuelle:
- Rajout de l'activation de la commande flood Bash
- Les deux variables RESTART et INIT ont été déplacées vers le module Irc $ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git
- Nouvelle class Install: $ cd IRC_DEFENDER_MODULES
- Le programme va vérifier si les 3 librairies sont installées (SQLAlchemy & requests & psutil) $ python3 -m venv .pyenv
- Une fois la vérification, il va mêtre a jour pip puis installera les dépendances $ source .pyenv/bin/activate
- Créer un service nommé "Defender.service" pour votre service et placer le dans "/etc/systemd/system/"
$ sudo systemctl start Defender
[28.12.2023] # Configuration
- 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 SERVEUR (Serveur)
SERVEUR_IP: Adresse IP du serveur IRC à rejoindre.
SERVEUR_HOSTNAME: Nom d'hôte du serveur IRC à rejoindre (optionnel).
SERVEUR_LINK: Lien vers le serveur IRC (optionnel).
SERVEUR_PORT: Port de connexion au serveur IRC.
SERVEUR_PASSWORD: Mot de passe d'enregistrement du service sur le serveur IRC.
SERVEUR_ID: Identifiant unique du service.
SERVEUR_SSL: Active la connexion SSL sécurisée au serveur IRC (true/false).
SERVICE (Service)
SERVICE_NAME: Nom du service IRC.
SERVICE_NICKNAME: Surnom utilisé par le service sur le serveur IRC.
SERVICE_REALNAME: Nom réel du service affiché sur le serveur IRC.
SERVICE_USERNAME: Nom d'utilisateur utilisé par le service pour se connecter au serveur IRC.
SERVICE_HOST: Nom d'hôte du service affiché sur le serveur IRC (optionnel).
SERVICE_INFO: Description du service.
SERVICE_CHANLOG: Canal utilisé pour la journalisation des actions du service.
SERVICE_SMODES: Modes serveur appliqués aux canaux rejoints par le service.
SERVICE_CMODES: Modes de canal appliqués aux canaux rejoints par le service.
SERVICE_UMODES: Modes utilisateur appliqués au service.
SERVICE_PREFIX: Caractère utilisé comme préfixe des commandes du service.
COMPTE (Compte)
OWNER: Nom d'utilisateur possédant les droits d'administration du service.
PASSWORD: Mot de passe de l'administrateur du service.
CANAUX (Canaux)
SALON_JAIL: Canal utilisé comme prison pour les utilisateurs sanctionnés.
SALON_JAIL_MODES: Modes appliqués au canal de prison.
SALON_LIBERER: Canal utilisé pour la libération des utilisateurs sanctionnés.
API (API)
API_TIMEOUT: Durée maximale d'attente d'une réponse de l'API en secondes.
SCANNER (Scanner)
PORTS_TO_SCAN: Liste des ports à scanner pour détecter des serveurs potentiellement malveillants.
SÉCURITÉ (Sécurité)
WHITELISTED_IP: Liste d'adresses IP autorisées à contourner certaines restrictions.
GLINE_DURATION: Durée de bannissement temporaire d'un utilisateur en minutes.
DEBUG (Debug)
DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus grand est le nombre, plus il y a d'informations).
COULEURS (Couleurs)
CONFIG_COLOR: Dictionnaire contenant des codes de couleurs IRC pour un meilleur affichage des messages.
[29.12.2023] Modification de la configuration
- 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 Vous devez modifier le fichier config.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.
- Connexion en tant que service Attention
- 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 Le mot de passe de l'administrateur et le mot de passe du service doivent être modifiés pour des raisons de sécurité.
- Activation ou désaction du systéme --> OK | .reputation ON/off Ne partagez pas vos informations de connexion au serveur IRC avec des tiers.
- 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 #Extension:
- Defender bannira l'utilisateur de la totalité des salons, il le bannira aussi lorsqu'il souhaitera accéder a de nouveau salon --> OK Le code est modulaire et conçu pour être facilement étendu. Vous pouvez ajouter de nouvelles commandes, de nouvelles fonctionnalités (mods/mod_test.py est un exemple pour bien demarrer la création de son module).
- 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 # Contributions:
- Defender devra liberer l'utilisateur et l'envoyer vers un salon définit dans la configuration --> OK Les contributions sont les bienvenues ! N'hésitez pas à ouvrir des issues ou des pull requests.
# Avertissement:
Ce bot est fourni "tel quel" sans aucune garantie. Utilisez-le à vos risques et périls.

View File

@@ -3,7 +3,6 @@ from datetime import datetime
from typing import Union from typing import Union
from core.base import Base from core.base import Base
class User: class User:
@dataclass @dataclass
@@ -18,7 +17,7 @@ class User:
remote_ip: str remote_ip: str
score_connexion: int score_connexion: int
connexion_datetime: datetime = field(default=datetime.now()) connexion_datetime: datetime = field(default=datetime.now())
UID_DB: list[UserModel] = [] UID_DB: list[UserModel] = []
def __init__(self, Base: Base) -> None: def __init__(self, Base: Base) -> None:
@@ -26,14 +25,23 @@ class User:
pass pass
def insert(self, newUser: UserModel) -> bool: def insert(self, newUser: UserModel) -> bool:
"""Insert a new User object
Args:
newUser (UserModel): New userModel object
Returns:
bool: True if inserted
"""
result = False result = False
exist = False exist = False
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == newUser.uid: if record.uid == newUser.uid:
# If the user exist then return False and do not go further
exist = True exist = True
self.log.debug(f'{record.uid} already exist') self.log.debug(f'{record.uid} already exist')
return result
if not exist: if not exist:
self.UID_DB.append(newUser) self.UID_DB.append(newUser)
@@ -44,39 +52,65 @@ class User:
self.log.critical(f'The User Object was not inserted {newUser}') self.log.critical(f'The User Object was not inserted {newUser}')
return result return result
def update(self, uid: str, newNickname: str) -> bool:
def update(self, uid: str, newNickname: str) -> bool:
"""Update the nickname starting from the UID
Args:
uid (str): UID of the user
newNickname (str): New nickname
Returns:
bool: True if updated
"""
result = False result = False
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uid: if record.uid == uid:
# If the user exist then update and return True and do not go further
record.nickname = newNickname record.nickname = newNickname
result = True result = True
self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}') self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}')
return result
if not result: if not result:
self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}') self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}')
return result return result
def delete(self, uid: str) -> bool:
def delete(self, uid: str) -> bool:
"""Delete the User starting from the UID
Args:
uid (str): UID of the user
Returns:
bool: True if deleted
"""
result = False result = False
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uid: if record.uid == uid:
# If the user exist then remove and return True and do not go further
self.UID_DB.remove(record) self.UID_DB.remove(record)
result = True result = True
self.log.debug(f'UID ({record.uid}) has been deleted') self.log.debug(f'UID ({record.uid}) has been deleted')
return result
if not result: if not result:
self.log.critical(f'The UID {uid} was not deleted') self.log.critical(f'The UID {uid} was not deleted')
return result return result
def get_User(self, uidornickname: str) -> Union[UserModel, None]:
def get_User(self, uidornickname: str) -> Union[UserModel, None]:
"""Get The User Object model
Args:
uidornickname (str): UID or Nickname
Returns:
UserModel|None: The UserModel Object | None
"""
User = None User = None
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
@@ -89,7 +123,14 @@ class User:
return User return User
def get_uid(self, uidornickname:str) -> Union[str, None]: def get_uid(self, uidornickname:str) -> Union[str, None]:
"""Get the UID of the user starting from the UID or the Nickname
Args:
uidornickname (str): UID or Nickname
Returns:
str|None: Return the UID
"""
uid = None uid = None
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
@@ -101,14 +142,21 @@ class User:
return uid return uid
def get_nickname(self, uidornickname:str) -> Union[str, None]: def get_nickname(self, uidornickname:str) -> Union[str, None]:
"""Get the Nickname starting from UID or the nickname
Args:
uidornickname (str): UID or Nickname of the user
Returns:
str|None: the nickname
"""
nickname = None nickname = None
for record in self.UID_DB: for record in self.UID_DB:
if record.nickname == uidornickname: if record.nickname == uidornickname:
nickname = record.nickname nickname = record.nickname
if record.uid == uidornickname: if record.uid == uidornickname:
nickname = record.nickname nickname = record.nickname
self.log.debug(f'The value {uidornickname} -- {nickname}') self.log.debug(f'The value to check {uidornickname} -> {nickname}')
return nickname return nickname
class Admin: class Admin:
@@ -137,8 +185,10 @@ class Admin:
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == newAdmin.uid: if record.uid == newAdmin.uid:
# If the admin exist then return False and do not go further
exist = True exist = True
self.log.debug(f'{record.uid} already exist') self.log.debug(f'{record.uid} already exist')
return result
if not exist: if not exist:
self.UID_ADMIN_DB.append(newAdmin) self.UID_ADMIN_DB.append(newAdmin)
@@ -149,37 +199,41 @@ class Admin:
self.log.critical(f'The User Object was not inserted {newAdmin}') self.log.critical(f'The User Object was not inserted {newAdmin}')
return result return result
def update(self, uid: str, newNickname: str) -> bool: def update(self, uid: str, newNickname: str) -> bool:
result = False result = False
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == uid: if record.uid == uid:
# If the admin exist, update and do not go further
record.nickname = newNickname record.nickname = newNickname
result = True result = True
self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}') self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}')
return result
if not result: if not result:
self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}') self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}')
return result return result
def delete(self, uid: str) -> bool: def delete(self, uid: str) -> bool:
result = False result = False
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == uid: if record.uid == uid:
# If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record) self.UID_ADMIN_DB.remove(record)
result = True result = True
self.log.debug(f'UID ({record.uid}) has been created') self.log.debug(f'UID ({record.uid}) has been created')
return result
if not result: if not result:
self.log.critical(f'The UID {uid} was not deleted') self.log.critical(f'The UID {uid} was not deleted')
return result return result
def get_Admin(self, uidornickname: str) -> Union[AdminModel, None]: def get_Admin(self, uidornickname: str) -> Union[AdminModel, None]:
Admin = None Admin = None
@@ -217,28 +271,37 @@ class Admin:
return nickname return nickname
class Channel: class Channel:
@dataclass @dataclass
class ChannelModel: class ChannelModel:
name: str name: str
mode: str
uids: list uids: list
UID_CHANNEL_DB: list[ChannelModel] = [] UID_CHANNEL_DB: list[ChannelModel] = []
def __init__(self, Base: Base) -> None: def __init__(self, Base: Base) -> None:
self.log = Base.logs self.log = Base.logs
self.Base = Base
pass pass
def insert(self, newChan: ChannelModel) -> bool: def insert(self, newChan: ChannelModel) -> bool:
"""This method will insert a new channel and if the channel exist it will update the user list (uids)
Args:
newChan (ChannelModel): The channel model object
Returns:
bool: True if new channel, False if channel exist (However UID could be updated)
"""
result = False result = False
exist = False exist = False
for record in self.UID_CHANNEL_DB: for record in self.UID_CHANNEL_DB:
if record.name == newChan.name: if record.name == newChan.name:
# If the channel exist, update the user list and do not go further
exist = True exist = True
self.log.debug(f'{record.name} already exist') self.log.debug(f'{record.name} already exist')
for user in newChan.uids: for user in newChan.uids:
record.uids.append(user) record.uids.append(user)
@@ -246,9 +309,11 @@ class Channel:
del_duplicates = list(set(record.uids)) del_duplicates = list(set(record.uids))
record.uids = del_duplicates record.uids = del_duplicates
self.log.debug(f'Updating a new UID to the channel {record}') self.log.debug(f'Updating a new UID to the channel {record}')
return result
if not exist: if not exist:
# If the channel don't exist, then create it
self.UID_CHANNEL_DB.append(newChan) self.UID_CHANNEL_DB.append(newChan)
result = True result = True
self.log.debug(f'New Channel Created: ({newChan})') self.log.debug(f'New Channel Created: ({newChan})')
@@ -257,48 +322,46 @@ class Channel:
self.log.critical(f'The Channel Object was not inserted {newChan}') self.log.critical(f'The Channel Object was not inserted {newChan}')
return result return result
def update(self, name: str, newMode: str) -> bool:
result = False
for record in self.UID_CHANNEL_DB:
if record.name == name:
record.mode = newMode
result = True
self.log.debug(f'Mode ({record.name}) has been updated with new mode {newMode}')
if not result:
self.log.critical(f'The channel mode {newMode} was not updated, name = {name}')
return result
def delete(self, name: str) -> bool: def delete(self, name: str) -> bool:
result = False result = False
for record in self.UID_CHANNEL_DB: for record in self.UID_CHANNEL_DB:
if record.name == name: if record.name == name:
# If the channel exist, then remove it and return True.
# As soon as the channel found, return True and stop the loop
self.UID_CHANNEL_DB.remove(record) self.UID_CHANNEL_DB.remove(record)
result = True result = True
self.log.debug(f'Channel ({record.name}) has been created') self.log.debug(f'Channel ({record.name}) has been created')
return result
if not result: if not result:
self.log.critical(f'The Channel {name} was not deleted') self.log.critical(f'The Channel {name} was not deleted')
return result return result
def delete_user_from_channel(self,chan_name: str, uid:str) -> bool:
result = False
for record in self.UID_CHANNEL_DB: def delete_user_from_channel(self, chan_name: str, uid:str) -> bool:
if record.name == chan_name: try:
record.uids.remove(uid) result = False
self.log.debug(f'uid {uid} has been removed, here is the new object: {record}')
result = True for record in self.UID_CHANNEL_DB:
if record.name == chan_name:
for user_id in record.uids:
if self.Base.clean_uid(user_id) == uid:
record.uids.remove(user_id)
self.log.debug(f'The UID {uid} has been removed, here is the new object: {record}')
result = True
for record in self.UID_CHANNEL_DB:
if not record.uids:
self.UID_CHANNEL_DB.remove(record)
self.log.debug(f'The Channel {record.name} has been removed, here is the new object: {record}')
return result
except ValueError as ve:
self.log.error(f'{ve}')
return result
def get_Channel(self, name: str) -> Union[ChannelModel, None]: def get_Channel(self, name: str) -> Union[ChannelModel, None]:
Channel = None Channel = None
@@ -309,14 +372,3 @@ class Channel:
self.log.debug(f'Search {name} -- result = {Channel}') self.log.debug(f'Search {name} -- result = {Channel}')
return Channel return Channel
def get_mode(self, name:str) -> Union[str, None]:
mode = None
for record in self.UID_CHANNEL_DB:
if record.name == name:
mode = record.mode
self.log.debug(f'The mode of the channel {name} has been found: {mode}')
return mode

View File

@@ -56,9 +56,9 @@ class Base:
} }
if token == '': if token == '':
response = requests.get(json_url) response = requests.get(json_url, timeout=self.Config.API_TIMEOUT)
else: else:
response = requests.get(json_url, headers=headers) response = requests.get(json_url, headers=headers, timeout=self.Config.API_TIMEOUT)
response.raise_for_status() # Vérifie si la requête a réussi response.raise_for_status() # Vérifie si la requête a réussi
json_response:dict = response.json() json_response:dict = response.json()
@@ -120,6 +120,16 @@ class Base:
currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S') currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S')
return currentdate return currentdate
def get_all_modules(self) -> list:
all_files = os.listdir('mods/')
all_modules: list = []
for module in all_files:
if module.endswith('.py') and not module == '__init__.py':
all_modules.append(module.replace('.py', '').lower())
return all_modules
def create_log(self, log_message: str) -> None: def create_log(self, log_message: str) -> None:
"""Enregiste les logs """Enregiste les logs
@@ -302,7 +312,7 @@ class Base:
if thread.getName() != 'heartbeat': if thread.getName() != 'heartbeat':
if not thread.is_alive(): if not thread.is_alive():
self.running_threads.remove(thread) self.running_threads.remove(thread)
self.logs.debug(f"Thread {str(thread.getName())} {str(thread.native_id)} removed") self.logs.info(f"Thread {str(thread.getName())} {str(thread.native_id)} removed")
# print(threading.enumerate()) # print(threading.enumerate())
except AssertionError as ae: except AssertionError as ae:
@@ -522,7 +532,7 @@ class Base:
parsed_UID = re.sub(pattern, '', uid) parsed_UID = re.sub(pattern, '', uid)
return parsed_UID return parsed_UID
def Is_Channel(self, channelToCheck: str) -> bool: def Is_Channel(self, channelToCheck: str) -> bool:
"""Check if the string has the # caractere and return True if this is a channel """Check if the string has the # caractere and return True if this is a channel

274
core/dataClass.py Normal file
View File

@@ -0,0 +1,274 @@
from dataclasses import dataclass, field
from datetime import datetime
from typing import Union
class User:
@dataclass
class UserDataClass:
uid: str
nickname: str
username: str
hostname: str
umodes: str
vhost: str
isWebirc: bool
connexion_datetime: datetime = field(default=datetime.now())
UID_DB:list[UserDataClass] = []
def __init__(self) -> None:
pass
def insert(self, user: UserDataClass) -> bool:
"""Insert new user
Args:
user (UserDataClass): The User dataclass
Returns:
bool: True if the record has been created
"""
exists = False
inserted = False
for record in self.UID_DB:
if record.uid == user.uid:
exists = True
print(f'{user.uid} already exist')
if not exists:
self.UID_DB.append(user)
print(f'New record with uid: {user.uid}')
inserted = True
return inserted
def update(self, uid: str, newnickname: str) -> bool:
"""Updating a single record with a new nickname
Args:
uid (str): the uid of the user
newnickname (str): the new nickname
Returns:
bool: True if the record has been updated
"""
status = False
for user in self.UID_DB:
if user.uid == uid:
user.nickname = newnickname
status = True
print(f'Updating record with uid: {uid}')
return status
def delete(self, uid: str) -> bool:
"""Delete a user based on his uid
Args:
uid (str): The UID of the user
Returns:
bool: True if the record has been deleted
"""
status = False
for user in self.UID_DB:
if user.uid == uid:
self.UID_DB.remove(user)
status = True
print(f'Removing record with uid: {uid}')
return status
def isexist(self, uidornickname:str) -> bool:
"""do the UID or Nickname exist ?
Args:
uidornickname (str): The UID or the Nickname
Returns:
bool: True if exist or False if don't exist
"""
result = False
for record in self.UID_DB:
if record.uid == uidornickname:
result = True
if record.nickname == uidornickname:
result = True
return result
def get_User(self, uidornickname) -> Union[UserDataClass, None]:
UserObject = None
for record in self.UID_DB:
if record.uid == uidornickname:
UserObject = record
elif record.nickname == uidornickname:
UserObject = record
return UserObject
def get_uid(self, uidornickname:str) -> Union[str, None]:
uid = None
for record in self.UID_DB:
if record.uid == uidornickname:
uid = record.uid
if record.nickname == uidornickname:
uid = record.uid
return uid
def get_nickname(self, uidornickname:str) -> Union[str, None]:
nickname = None
for record in self.UID_DB:
if record.nickname == uidornickname:
nickname = record.nickname
if record.uid == uidornickname:
nickname = record.nickname
return nickname
class Admin:
@dataclass
class AdminDataClass:
uid: str
nickname: str
username: str
hostname: str
umodes: str
vhost: str
level: int
connexion_datetime: datetime = field(default=datetime.now())
UID_ADMIN_DB:list[AdminDataClass] = []
def __init__(self) -> None:
pass
def insert(self, admin: AdminDataClass) -> bool:
"""Insert new user
Args:
user (UserDataClass): The User dataclass
Returns:
bool: True if the record has been created
"""
exists = False
inserted = False
for record in self.UID_ADMIN_DB:
if record.uid == admin.uid:
exists = True
print(f'{admin.uid} already exist')
if not exists:
self.UID_ADMIN_DB.append(admin)
print(f'New record with uid: {admin.uid}')
inserted = True
return inserted
def update(self, uid: str, newnickname: str) -> bool:
"""Updating a single record with a new nickname
Args:
uid (str): the uid of the user
newnickname (str): the new nickname
Returns:
bool: True if the record has been updated
"""
status = False
for admin in self.UID_ADMIN_DB:
if admin.uid == uid:
admin.nickname = newnickname
status = True
print(f'Updating record with uid: {uid}')
return status
def delete(self, uid: str) -> bool:
"""Delete a user based on his uid
Args:
uid (str): The UID of the user
Returns:
bool: True if the record has been deleted
"""
status = False
for admin in self.UID_ADMIN_DB:
if admin.uid == uid:
self.UID_ADMIN_DB.remove(admin)
status = True
print(f'Removing record with uid: {uid}')
return status
def isexist(self, uidornickname:str) -> bool:
"""do the UID or Nickname exist ?
Args:
uidornickname (str): The UID or the Nickname
Returns:
bool: True if exist or False if don't exist
"""
result = False
for record in self.UID_ADMIN_DB:
if record.uid == uidornickname:
result = True
if record.nickname == uidornickname:
result = True
return result
def get_Admin(self, uidornickname) -> Union[AdminDataClass, None]:
AdminObject = None
for record in self.UID_ADMIN_DB:
if record.uid == uidornickname:
AdminObject = record
elif record.nickname == uidornickname:
AdminObject = record
return AdminObject
def get_uid(self, uidornickname:str) -> Union[str, None]:
uid = None
for record in self.UID_ADMIN_DB:
if record.uid == uidornickname:
uid = record.uid
if record.nickname == uidornickname:
uid = record.uid
return uid
def get_nickname(self, uidornickname:str) -> Union[str, None]:
nickname = None
for record in self.UID_ADMIN_DB:
if record.nickname == uidornickname:
nickname = record.nickname
if record.uid == uidornickname:
nickname = record.nickname
return nickname
def get_level(self, uidornickname:str) -> int:
level = 0
for record in self.UID_ADMIN_DB:
if record.uid == uidornickname:
level = record.level
if record.nickname == uidornickname:
level = record.level
return level

View File

@@ -1,12 +1,16 @@
from importlib.util import find_spec from importlib.util import find_spec
from subprocess import check_call, run from subprocess import check_call, run, CalledProcessError
from platform import python_version from platform import python_version
from sys import exit from sys import exit
import os
class Install: class Install:
def __init__(self) -> None: def __init__(self) -> None:
self.PYTHON_MIN_VERSION = '3.10' self.PYTHON_MIN_VERSION = '3.10'
self.venv_folder_name = '.pyenv'
self.cmd_venv_command = ['python3', '-m', 'venv', self.venv_folder_name]
self.module_to_install = ['sqlalchemy','psutil','requests'] self.module_to_install = ['sqlalchemy','psutil','requests']
if not self.checkPythonVersion(): if not self.checkPythonVersion():
@@ -38,6 +42,17 @@ class Install:
return True return True
def run_subprocess(self, command:list) -> None:
print(command)
try:
check_call(command)
print("La commande s'est terminée avec succès.")
except CalledProcessError as e:
print(f"La commande a échoué avec le code de retour :{e.returncode}")
print(f"Try to install dependencies ...")
exit(5)
def checkDependencies(self) -> None: def checkDependencies(self) -> None:
"""### Verifie les dépendances si elles sont installées """### Verifie les dépendances si elles sont installées
- Test si les modules sont installés - Test si les modules sont installés
@@ -46,6 +61,11 @@ class Install:
""" """
do_install = False do_install = False
# Check if virtual env exist
if not os.path.exists(f'{self.venv_folder_name}'):
self.run_subprocess(self.cmd_venv_command)
do_install = True
for module in self.module_to_install: for module in self.module_to_install:
if find_spec(module) is None: if find_spec(module) is None:
do_install = True do_install = True
@@ -70,3 +90,10 @@ class Install:
print(f"====> Module {module} installé") print(f"====> Module {module} installé")
else: else:
print(f"==> {module} already installed") print(f"==> {module} already installed")
print(f"#"*12)
print("Installation complete ...")
print("You must change environment using the command below")
print(f"source {self.venv_folder_name}{os.sep}bin{os.sep}activate")
print(f"#"*12)
exit(1)

View File

@@ -713,17 +713,13 @@ class Irc:
case 'MODE': case 'MODE':
#['@msgid=d0ySx56Yd0nc35oHts2SkC-/J9mVUA1hfM6+Z4494xWUg;time=2024-08-09T12:45:36.651Z', #['@msgid=d0ySx56Yd0nc35oHts2SkC-/J9mVUA1hfM6+Z4494xWUg;time=2024-08-09T12:45:36.651Z',
# ':001', 'MODE', '#a', '+nt', '1723207536'] # ':001', 'MODE', '#a', '+nt', '1723207536']
cmd.pop(0) pass
if '#' in cmd[2]:
channel = cmd[2]
mode = cmd[3]
self.Channel.update(channel, mode)
case 'SJOIN': case 'SJOIN':
# ['@msgid=5sTwGdj349D82L96p749SY;time=2024-08-15T09:50:23.528Z', ':001', 'SJOIN', '1721564574', '#welcome', ':001JD94QH'] # ['@msgid=5sTwGdj349D82L96p749SY;time=2024-08-15T09:50:23.528Z', ':001', 'SJOIN', '1721564574', '#welcome', ':001JD94QH']
# ['@msgid=bvceb6HthbLJapgGLXn1b0;time=2024-08-15T09:50:11.464Z', ':001', 'SJOIN', '1721564574', '#welcome', '+lnrt', '13', ':001CIVLQF', '+11ZAAAAAB', '001QGR10C', '*@0014UE10B', '001NL1O07', '001SWZR05', '001HB8G04', '@00BAAAAAJ', '0019M7101'] # ['@msgid=bvceb6HthbLJapgGLXn1b0;time=2024-08-15T09:50:11.464Z', ':001', 'SJOIN', '1721564574', '#welcome', '+lnrt', '13', ':001CIVLQF', '+11ZAAAAAB', '001QGR10C', '*@0014UE10B', '001NL1O07', '001SWZR05', '001HB8G04', '@00BAAAAAJ', '0019M7101']
cmd.pop(0) cmd.pop(0)
channel = cmd[3] channel = str(cmd[3]).lower()
mode = cmd[4] mode = cmd[4]
len_cmd = len(cmd) len_cmd = len(cmd)
list_users:list = [] list_users:list = []
@@ -747,7 +743,6 @@ class Irc:
self.Channel.insert( self.Channel.insert(
self.Channel.ChannelModel( self.Channel.ChannelModel(
name=channel, name=channel,
mode=mode,
uids=list_users uids=list_users
) )
) )
@@ -756,7 +751,7 @@ class Irc:
# ['@unrealircd.org/geoip=FR;unrealircd.org/userhost=50d6492c@80.214.73.44;unrealircd.org/userip=50d6492c@80.214.73.44;msgid=YSIPB9q4PcRu0EVfC9ci7y-/mZT0+Gj5FLiDSZshH5NCw;time=2024-08-15T15:35:53.772Z', # ['@unrealircd.org/geoip=FR;unrealircd.org/userhost=50d6492c@80.214.73.44;unrealircd.org/userip=50d6492c@80.214.73.44;msgid=YSIPB9q4PcRu0EVfC9ci7y-/mZT0+Gj5FLiDSZshH5NCw;time=2024-08-15T15:35:53.772Z',
# ':001EPFBRD', 'PART', '#welcome', ':WEB', 'IRC', 'Paris'] # ':001EPFBRD', 'PART', '#welcome', ':WEB', 'IRC', 'Paris']
uid = str(cmd[1]).replace(':','') uid = str(cmd[1]).replace(':','')
channel = str(cmd[3]) channel = str(cmd[3]).lower()
self.Channel.delete_user_from_channel(channel, uid) self.Channel.delete_user_from_channel(channel, uid)
pass pass
@@ -808,10 +803,10 @@ class Irc:
get_uid_or_nickname = str(cmd[0].replace(':','')) get_uid_or_nickname = str(cmd[0].replace(':',''))
if len(cmd) == 6: if len(cmd) == 6:
if cmd[1] == 'PRIVMSG' and cmd[3] == ':auth': if cmd[1] == 'PRIVMSG' and str(cmd[3]).replace('.','') == ':auth':
cmd_copy = cmd.copy() cmd_copy = cmd.copy()
cmd_copy[5] = '**********' cmd_copy[5] = '**********'
self.Base.logs.debug(cmd_copy) self.Base.logs.info(cmd_copy)
else: else:
self.Base.logs.info(cmd) self.Base.logs.info(cmd)
else: else:
@@ -1213,16 +1208,31 @@ class Irc:
case 'show_modules': case 'show_modules':
self.Base.logs.debug(self.loaded_classes) self.Base.logs.debug(self.loaded_classes)
all_modules = self.Base.get_all_modules()
results = self.Base.db_execute_query(f'SELECT module FROM {self.Config.table_module}') results = self.Base.db_execute_query(f'SELECT module FROM {self.Config.table_module}')
results = results.fetchall() results = results.fetchall()
if len(results) == 0: # if len(results) == 0:
self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Aucun module chargé") # self.send2socket(f":{dnickname} NOTICE {fromuser} :There is no module loaded")
return False # return False
for r in results: found = False
self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Le module {r[0]} chargé")
for module in all_modules:
for loaded_mod in results:
if module == loaded_mod[0]:
found = True
if found:
self.send2socket(f":{dnickname} NOTICE {fromuser} :{module} - {self.Config.CONFIG_COLOR['verte']}Loaded{self.Config.CONFIG_COLOR['nogc']}")
else:
self.send2socket(f":{dnickname} NOTICE {fromuser} :{module} - {self.Config.CONFIG_COLOR['rouge']}Not Loaded{self.Config.CONFIG_COLOR['nogc']}")
found = False
# for r in results:
# self.send2socket(f":{dnickname} NOTICE {fromuser} :{r[0]} - {self.Config.CONFIG_COLOR['verte']}Loaded{self.Config.CONFIG_COLOR['nogc']}")
case 'show_timers': case 'show_timers':

263
install.py Normal file
View File

@@ -0,0 +1,263 @@
from subprocess import check_call, run, CalledProcessError, PIPE
from platform import python_version
from sys import exit
import os, logging, shutil, pwd
class Install:
def __init__(self) -> None:
# Python required version
self.python_min_version = '3.10'
self.log_file = 'install.log'
self.ServiceName = 'Defender'
self.venv_name = '.pyenv'
self.venv_dependencies: list[str] = ['sqlalchemy','psutil','requests']
self.install_folder = os.getcwd()
self.osname = os.name
self.cmd_linux_requirements: list[str] = ['apt', 'install', '-y', 'python3', 'python3-pip', 'python3-venv']
self.venv_pip_full_path = os.path.join(self.venv_name, f'bin{os.sep}pip')
self.venv_python_full_path = os.path.join(self.venv_name, f'bin{os.sep}python')
self.systemd_folder = '/etc/systemd/system/'
# Init log system
self.init_log_system()
# Exclude Windows OS
if self.osname == 'nt':
print('/!\\ Windows OS is not supported by this automatic installation /!\\')
self.Logs.critical('/!\\ Windows OS is not supported by this automatic install /!\\')
exit(5)
if not self.is_root():
exit(5)
# Get the current user
self.system_username: str = input(f'What is the user ro run defender with ? [{os.getlogin()}] : ')
if str(self.system_username).strip() == '':
self.system_username = os.getlogin()
self.get_user_information(self.system_username)
self.Logs.debug(f'The user selected is: {self.system_username}')
self.Logs.debug(f'Operating system: {self.osname}')
# Install linux dependencies
self.install_linux_dependencies()
# Check python version
self.checkPythonVersion()
# Create systemd service file
self.create_service_file()
# Check if Env Exist | install environment | Install python dependencies
self.check_venv()
# Create and start service
if self.osname != 'nt':
self.run_subprocess(['systemctl','daemon-reload'])
self.run_subprocess(['systemctl','start', self.ServiceName])
self.run_subprocess(['systemctl','status', self.ServiceName])
# Clean the Installation
self.clean_installation()
return None
def is_installed(self) -> bool:
is_installed = False
# Check logs folder
if os.path.exists('logs'):
is_installed = True
# Check db folder
if os.path.exists('db'):
is_installed = True
return is_installed
def is_root(self) -> bool:
if os.geteuid() != 0:
print('/!\\ user must run install.py as root /!\\')
self.Logs.critical('/!\\ user must run install.py as root /!\\')
return False
elif os.geteuid() == 0:
return True
def get_user_information(self, system_user: str) -> None:
try:
username: tuple = pwd.getpwnam(system_user)
self.system_uid = username.pw_uid
self.system_gid = username.pw_gid
return None
except KeyError as ke:
self.Logs.critical(f"This user [{system_user}] doesn't exist: {ke}")
print(f"This user [{system_user}] doesn't exist: {ke}")
exit(5)
def init_log_system(self) -> None:
# Init logs object
self.Logs = logging
self.Logs.basicConfig(level=logging.DEBUG,
filename=self.log_file,
encoding='UTF-8',
format='%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s')
self.Logs.debug('#################### STARTING INSTALLATION ####################')
return None
def clean_installation(self) -> None:
# Chown the Python Env to non user privilege
self.run_subprocess(['chown','-R', f'{self.system_username}:{self.system_username}',
f'{os.path.join(self.install_folder, self.venv_name)}'
]
)
# Chown the installation log file
self.run_subprocess(['chown','-R', f'{self.system_username}:{self.system_username}',
f'{os.path.join(self.install_folder, self.log_file)}'
]
)
return None
def run_subprocess(self, command:list) -> None:
try:
run_command = check_call(command)
self.Logs.debug(f'{command} - {run_command}')
print(f'{command} - {run_command}')
except CalledProcessError as e:
print(f"Command failed :{e.returncode}")
self.Logs.critical(f"Command failed :{e.returncode}")
exit(5)
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('.')
self.Logs.debug(f'The current python version is: {python_version()}')
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} ##")
self.Logs.critical(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} ###")
self.Logs.critical(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")
self.Logs.debug(f'Version of python : {python_version()} ==> OK')
return True
def check_packages(self, package_name) -> bool:
try:
# Run a command in the virtual environment's Python to check if the package is installed
run([self.venv_python_full_path, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE)
return True
except CalledProcessError:
return False
def check_venv(self) -> bool:
if os.path.exists(self.venv_name):
# Installer les dependances
self.install_dependencies()
return True
else:
self.run_subprocess(['python3', '-m', 'venv', self.venv_name])
self.Logs.debug(f'Python Virtual env installed {self.venv_name}')
print(f'Python Virtual env installed {self.venv_name}')
self.install_dependencies()
return False
def create_service_file(self) -> None:
if self.systemd_folder is None:
# If Windows, do not install systemd
return None
if os.path.exists(f'{self.systemd_folder}{os.sep}{self.ServiceName}.service'):
print(f'/!\\ Service already created in the system /!\\')
self.Logs.warning('/!\\ Service already created in the system /!\\')
print(f'The service file will be regenerated')
self.Logs.warning('The service file will be regenerated')
contain = f'''[Unit]
Description={self.ServiceName} IRC Service
[Service]
User={self.system_username}
ExecStart={os.path.join(self.install_folder, self.venv_python_full_path)} {os.path.join(self.install_folder, 'main.py')}
WorkingDirectory={self.install_folder}
SyslogIdentifier={self.ServiceName}
Restart=on-failure
[Install]
WantedBy=multi-user.target
'''
with open(f'{self.ServiceName}.service.generated', 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print('Service file generated with current configuration')
self.Logs.debug('Service file generated with current configuration')
source = f'{self.install_folder}{os.sep}{self.ServiceName}.service.generated'
self.run_subprocess(['chown','-R', f'{self.system_username}:{self.system_username}', source])
destination = f'{self.systemd_folder}'
shutil.copy(source, destination)
os.rename(f'{self.systemd_folder}{os.sep}{self.ServiceName}.service.generated', f'{self.systemd_folder}{os.sep}{self.ServiceName}.service')
print(f'Service file moved to systemd folder {self.systemd_folder}')
self.Logs.debug(f'Service file moved to systemd folder {self.systemd_folder}')
def install_linux_dependencies(self) -> None:
self.run_subprocess(self.cmd_linux_requirements)
return None
def install_dependencies(self) -> None:
try:
self.run_subprocess([self.venv_pip_full_path, 'cache', 'purge'])
self.run_subprocess([self.venv_python_full_path, '-m', 'pip', 'install', '--upgrade', 'pip'])
if self.check_packages('greenlet') is None:
self.run_subprocess(
[self.venv_pip_full_path, 'install', '--only-binary', ':all:', 'greenlet']
)
for module in self.venv_dependencies:
if not self.check_packages(module):
### Trying to install missing python packages ###
self.run_subprocess([self.venv_pip_full_path, 'install', module])
else:
self.Logs.debug(f'{module} already installed')
print(f"==> {module} already installed")
except CalledProcessError as cpe:
self.Logs.critical(f'{cpe}')
Install()

View File

@@ -80,7 +80,7 @@ class Defender():
self.commands_level = { self.commands_level = {
0: ['code'], 0: ['code'],
1: ['join','part', 'info'], 1: ['join','part', 'info'],
2: ['q', 'dq', 'o', 'do', 'h', 'dh', 'v', 'dv', 'b', 'ub','k', 'kb'], 2: ['owner', 'deowner', 'op', 'deop', 'halfop', 'dehalfop', 'voice', 'devoice', 'ban', 'unban','kick', 'kickban'],
3: ['reputation','proxy_scan', 'flood', 'status', 'timer','show_reputation', 'show_users', 'sentinel'] 3: ['reputation','proxy_scan', 'flood', 'status', 'timer','show_reputation', 'show_users', 'sentinel']
} }
self.__set_commands(self.commands_level) # Enrigstrer les nouvelles commandes dans le code self.__set_commands(self.commands_level) # Enrigstrer les nouvelles commandes dans le code
@@ -1224,6 +1224,23 @@ class Defender():
jail_chan = self.Config.SALON_JAIL # Salon pot de miel jail_chan = self.Config.SALON_JAIL # Salon pot de miel
jail_chan_mode = self.Config.SALON_JAIL_MODES # Mode du salon "pot de miel" jail_chan_mode = self.Config.SALON_JAIL_MODES # Mode du salon "pot de miel"
if len(fullcmd) >= 3:
fromchannel = str(fullcmd[2]).lower() if self.Base.Is_Channel(str(fullcmd[2]).lower()) else None
else:
fromchannel = None
if len(cmd) >= 2:
sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None
else:
sentchannel = None
if not fromchannel is None:
channel = fromchannel
elif not sentchannel is None:
channel = sentchannel
else:
channel = None
match command: match command:
case 'timer': case 'timer':
@@ -1606,97 +1623,209 @@ class Defender():
except IndexError as ie: except IndexError as ie:
self.Logs.error(f'{ie}') self.Logs.error(f'{ie}')
case 'op' | 'o': case 'op':
# /mode #channel +o user # /mode #channel +o user
# .op #channel user # .op #channel user
# /msg dnickname op #channel user
# [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb'] # [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb']
try: try:
print(cmd) if channel is None:
channel = cmd[1] self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} +o {fromuser}")
return True
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd OP: {str(e)}') self.Logs.warning(f'_hcmd OP: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]')
case 'deop' | 'do': case 'deop':
# /mode #channel -o user # /mode #channel -o user
# .deop #channel user # .deop #channel user
try: try:
channel = cmd[1] if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} -o {fromuser}")
return True
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd DEOP: {str(e)}') self.Logs.warning(f'_hcmd DEOP: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]')
case 'owner' | 'q': case 'owner':
# /mode #channel +q user # /mode #channel +q user
# .owner #channel user # .owner #channel user
try: try:
channel = cmd[1] if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} +q {fromuser}")
return True
# owner nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd OWNER: {str(e)}') self.Logs.warning(f'_hcmd OWNER: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]')
case 'deowner' | 'dq': case 'deowner':
# /mode #channel -q user # /mode #channel -q user
# .deowner #channel user # .deowner #channel user
try: try:
if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} -q {fromuser}")
return True
# deowner nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}")
return True
channel = cmd[1] channel = cmd[1]
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') self.Logs.warning(f'_hcmd DEOWNER: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]')
case 'halfop' | 'h': case 'halfop':
# /mode #channel +h user # /mode #channel +h user
# .halfop #channel user # .halfop #channel user
try: try:
channel = cmd[1] if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} +h {fromuser}")
return True
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd halfop: {str(e)}') self.Logs.warning(f'_hcmd halfop: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]')
case 'dehalfop' | 'dh': case 'dehalfop':
# /mode #channel -h user # /mode #channel -h user
# .dehalfop #channel user # .dehalfop #channel user
try: try:
channel = cmd[1] if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} -h {fromuser}")
return True
# dehalfop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}') self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]')
case 'voice' | 'v': case 'voice':
# /mode #channel +v user # /mode #channel +v user
# .voice #channel user # .voice #channel user
try: try:
channel = cmd[1] if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} +v {fromuser}")
return True
# voice nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd VOICE: {str(e)}') self.Logs.warning(f'_hcmd VOICE: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]')
case 'devoice' | 'dv': case 'devoice':
# /mode #channel -v user # /mode #channel -v user
# .devoice #channel user # .devoice #channel user
try: try:
channel = cmd[1] if channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {channel} -v {fromuser}")
return True
# dehalfop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}")
return True
nickname = cmd[2] nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}") self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd DEVOICE: {str(e)}') self.Logs.warning(f'_hcmd DEVOICE: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]')
case 'ban' | 'b': case 'ban':
# .ban #channel nickname # .ban #channel nickname
try: try:
channel = cmd[1] channel = cmd[1]
@@ -1708,7 +1837,7 @@ class Defender():
self.Logs.warning(f'_hcmd BAN: {str(e)}') self.Logs.warning(f'_hcmd BAN: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]')
case 'unban' | 'ub': case 'unban':
# .unban #channel nickname # .unban #channel nickname
try: try:
channel = cmd[1] channel = cmd[1]
@@ -1720,7 +1849,7 @@ class Defender():
self.Logs.warning(f'_hcmd UNBAN: {str(e)}') self.Logs.warning(f'_hcmd UNBAN: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]')
case 'kick' | 'k': case 'kick':
# .kick #channel nickname reason # .kick #channel nickname reason
try: try:
channel = cmd[1] channel = cmd[1]
@@ -1738,7 +1867,7 @@ class Defender():
self.Logs.warning(f'_hcmd KICK: {str(e)}') self.Logs.warning(f'_hcmd KICK: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]')
case 'kickban' | 'kb': case 'kickban':
# .kickban #channel nickname reason # .kickban #channel nickname reason
try: try:
channel = cmd[1] channel = cmd[1]

View File

@@ -157,7 +157,7 @@ class Votekick():
mes_donnees = {'channel': channel} mes_donnees = {'channel': channel}
response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees) response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees)
isChannelExist = response.fetchone() isChannelExist = response.fetchone()
if isChannelExist is None: if isChannelExist is None:
@@ -198,7 +198,7 @@ class Votekick():
self.Irc.send2socket(f":{self.Config.SERVEUR_ID} SJOIN {unixtime} {chan} + :{self.Config.SERVICE_ID}") self.Irc.send2socket(f":{self.Config.SERVEUR_ID} SJOIN {unixtime} {chan} + :{self.Config.SERVICE_ID}")
self.Irc.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {chan} +o {self.Config.SERVICE_NICKNAME}") self.Irc.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {chan} +o {self.Config.SERVICE_NICKNAME}")
return None return None
def is_vote_ongoing(self, channel: str) -> bool: def is_vote_ongoing(self, channel: str) -> bool:
@@ -207,9 +207,29 @@ class Votekick():
if vote.channel_name == channel: if vote.channel_name == channel:
if vote.target_user: if vote.target_user:
response = True response = True
return response return response
def timer_vote_verdict(self, channel: str) -> None:
dnickname = self.Config.SERVICE_NICKNAME
for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == channel:
target_user = self.User.get_nickname(chan.target_user)
if chan.vote_for > chan.vote_against:
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :The user {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} will be kicked from this channel')
self.Irc.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
self.Channel.delete_user_from_channel(channel, self.User.get_uid(target_user))
elif chan.vote_for <= chan.vote_against:
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user will stay on this channel')
# Init the system
if self.init_vote_system(channel):
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :System vote re initiated')
return None
def cmd(self, data:list) -> None: def cmd(self, data:list) -> None:
cmd = list(data).copy() cmd = list(data).copy()
@@ -227,16 +247,18 @@ class Votekick():
command = str(cmd[0]).lower() command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
fromuser = user
if len(fullcmd) >= 3: if len(fullcmd) >= 3:
fromchannel = str(fullcmd[2]).lower() if self.Base.Is_Channel(str(fullcmd[2]).lower()) else None fromchannel = str(fullcmd[2]).lower() if self.Base.Is_Channel(str(fullcmd[2]).lower()) else None
else: else:
fromchannel = None fromchannel = None
if len(cmd) >= 2: if len(cmd) >= 2:
sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None
else: else:
sentchannel = None sentchannel = None
if not fromchannel is None: if not fromchannel is None:
channel = fromchannel channel = fromchannel
elif not sentchannel is None: elif not sentchannel is None:
@@ -244,18 +266,14 @@ class Votekick():
else: else:
channel = None channel = None
fromuser = user
match command: match command:
case 'vote_cancel': case 'vote_cancel':
try: try:
if fromchannel is None: if channel is None:
channel = str(cmd[1]).lower() self.Logs.error(f"The channel is not known, defender can't cancel the vote")
else: self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :You need to specify the channel => /msg {dnickname} vote_cancel #channel')
channel = fromchannel
for vote in self.VOTE_CHANNEL_DB: for vote in self.VOTE_CHANNEL_DB:
if vote.channel_name == channel: if vote.channel_name == channel:
self.init_vote_system(channel) self.init_vote_system(channel)
@@ -280,6 +298,9 @@ class Votekick():
except KeyError as ke: except KeyError as ke:
self.Logs.error(f'Key Error: {ke}') self.Logs.error(f'Key Error: {ke}')
except IndexError as ie:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} vote_cancel #channel')
self.Logs.error(f'Index Error: {ie}')
case 'vote_against': case 'vote_against':
try: try:
@@ -299,7 +320,7 @@ class Votekick():
case 'vote_stat': case 'vote_stat':
try: try:
channel = str(fullcmd[2]).lower() # channel = str(fullcmd[2]).lower()
for chan in self.VOTE_CHANNEL_DB: for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == channel: if chan.channel_name == channel:
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :Channel: {chan.channel_name} | Target: {self.User.get_nickname(chan.target_user)} | For: {chan.vote_for} | Against: {chan.vote_against} | Number of voters: {str(len(chan.voter_users))}') self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :Channel: {chan.channel_name} | Target: {self.User.get_nickname(chan.target_user)} | For: {chan.vote_for} | Against: {chan.vote_against} | Number of voters: {str(len(chan.voter_users))}')
@@ -309,7 +330,7 @@ class Votekick():
case 'vote_verdict': case 'vote_verdict':
try: try:
channel = str(fullcmd[2]).lower() # channel = str(fullcmd[2]).lower()
for chan in self.VOTE_CHANNEL_DB: for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == channel: if chan.channel_name == channel:
target_user = self.User.get_nickname(chan.target_user) target_user = self.User.get_nickname(chan.target_user)
@@ -330,7 +351,7 @@ class Votekick():
# submit nickname # submit nickname
try: try:
nickname_submitted = cmd[1] nickname_submitted = cmd[1]
channel = str(fullcmd[2]).lower() # channel = str(fullcmd[2]).lower()
uid_submitted = self.User.get_uid(nickname_submitted) uid_submitted = self.User.get_uid(nickname_submitted)
user_submitted = self.User.get_User(nickname_submitted) user_submitted = self.User.get_User(nickname_submitted)
@@ -369,9 +390,12 @@ class Votekick():
for chan in self.VOTE_CHANNEL_DB: for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == channel: if chan.channel_name == channel:
chan.target_user = self.User.get_uid(nickname_submitted) chan.target_user = self.User.get_uid(nickname_submitted)
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :{nickname_submitted} has been targeted for a vote') self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :{nickname_submitted} has been targeted for a vote')
self.Base.create_timer(60, self.timer_vote_verdict, (channel, ))
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This vote will end after 60 secondes')
except KeyError as ke: except KeyError as ke:
self.Logs.error(f'Key Error: {ke}') self.Logs.error(f'Key Error: {ke}')
except TypeError as te: except TypeError as te:
@@ -380,7 +404,7 @@ class Votekick():
case 'activate': case 'activate':
try: try:
# activate #channel # activate #channel
channel = str(cmd[1]).lower() # channel = str(cmd[1]).lower()
self.insert_vote_channel( self.insert_vote_channel(
self.VoteChannelModel( self.VoteChannelModel(
@@ -402,7 +426,7 @@ class Votekick():
case 'deactivate': case 'deactivate':
try: try:
# deactivate #channel # deactivate #channel
channel = str(cmd[1]).lower() # channel = str(cmd[1]).lower()
self.Irc.send2socket(f":{dnickname} SAMODE {channel} -o {dnickname}") self.Irc.send2socket(f":{dnickname} SAMODE {channel} -o {dnickname}")
self.Irc.send2socket(f":{dnickname} PART {channel}") self.Irc.send2socket(f":{dnickname} PART {channel}")

View File

@@ -1,3 +1,3 @@
{ {
"version": "5.0.0" "version": "5.0.6"
} }