diff --git a/README.md b/README.md index 24984e1..1d1c647 100644 --- a/README.md +++ b/README.md @@ -2,70 +2,54 @@ 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. +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 - - Pip de python installé sur la machine - - Python librairies psutil & sqlalchemy & requests - - IRC Serveur Version >= UnrealIRCd-6.1.2.2 + Système de quarantaine: + Mise en quarantaine: Isoler temporairement un utilisateur dans un canal privé. + Libération: Permettre à un utilisateur de quitter la quarantaine en entrant un code spécifique. -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 - - 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. +# Installation et utilisation + 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 -# TO DO LIST + Installation: - - 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 + Cloner le dépôt: + Bash + git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git + Utilisez ce code avec précaution. -# VERSION 1 + Configuration (configuration.json): + Le fichier configuration.json permet de personnaliser le service: + Serveur IRC: Adresse du serveur IRC. + Port: Port du serveur IRC. + Canal: Canal auquel se connecter. + Nom du Service: Nom d'utilisateur du bot sur le serveur. + Mot de passe: Mot de passe du link (si nécessaire). + Préfixes de commandes: Caractères utilisés pour déclencher les commandes. + Et bien d'autres... - [02.01.2024] - - Rajout de l'activation de la commande flood - - Les deux variables RESTART et INIT ont été déplacées vers le module Irc - - Nouvelle class Install: - - Le programme va vérifier si les 3 librairies sont installées (SQLAlchemy & requests & psutil) - - Une fois la vérification, il va mêtre a jour pip puis installera les dépendances + Extension: + 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). - [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 + Contributions: + Les contributions sont les bienvenues ! N'hésitez pas à ouvrir des issues ou des pull requests. -# 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 + Avertissement: + Ce bot est fourni "tel quel" sans aucune garantie. Utilisez-le à vos risques et périls. \ No newline at end of file diff --git a/core/base.py b/core/base.py index 53efbe2..19d9340 100644 --- a/core/base.py +++ b/core/base.py @@ -56,9 +56,9 @@ class Base: } if token == '': - response = requests.get(json_url) + response = requests.get(json_url, timeout=self.Config.API_TIMEOUT) 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 json_response:dict = response.json() @@ -120,6 +120,16 @@ class Base: currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S') 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: """Enregiste les logs @@ -522,7 +532,7 @@ class Base: parsed_UID = re.sub(pattern, '', uid) return parsed_UID - + def Is_Channel(self, channelToCheck: str) -> bool: """Check if the string has the # caractere and return True if this is a channel diff --git a/core/irc.py b/core/irc.py index fb717b6..a472fdf 100644 --- a/core/irc.py +++ b/core/irc.py @@ -1208,16 +1208,31 @@ class Irc: case 'show_modules': 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 = results.fetchall() - if len(results) == 0: - self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Aucun module chargé") - return False + # if len(results) == 0: + # self.send2socket(f":{dnickname} NOTICE {fromuser} :There is no module loaded") + # return False - for r in results: - self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Le module {r[0]} chargé") + found = False + + 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': diff --git a/mods/mod_defender.py b/mods/mod_defender.py index 1b067f0..ca7e90c 100644 --- a/mods/mod_defender.py +++ b/mods/mod_defender.py @@ -80,7 +80,7 @@ class Defender(): self.commands_level = { 0: ['code'], 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'] } 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_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: case 'timer': @@ -1606,97 +1623,209 @@ class Defender(): except IndexError as ie: self.Logs.error(f'{ie}') - case 'op' | 'o': + case 'op': # /mode #channel +o user # .op #channel user + # /msg dnickname op #channel user # [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb'] try: - print(cmd) - channel = cmd[1] + if channel is None: + 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] self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd OP: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') - case 'deop' | 'do': + case 'deop': # /mode #channel -o user # .deop #channel user 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] self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd DEOP: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') - case 'owner' | 'q': + case 'owner': # /mode #channel +q user # .owner #channel user 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] self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd OWNER: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') - case 'deowner' | 'dq': + case 'deowner': # /mode #channel -q user # .deowner #channel user 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] nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') - case 'halfop' | 'h': + case 'halfop': # /mode #channel +h user # .halfop #channel user 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] self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd halfop: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') - case 'dehalfop' | 'dh': + case 'dehalfop': # /mode #channel -h user # .dehalfop #channel user 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] self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') - case 'voice' | 'v': + case 'voice': # /mode #channel +v user # .voice #channel user 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] self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd VOICE: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') - case 'devoice' | 'dv': + case 'devoice': # /mode #channel -v user # .devoice #channel user 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] self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}") + except IndexError as e: self.Logs.warning(f'_hcmd DEVOICE: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') - case 'ban' | 'b': + case 'ban': # .ban #channel nickname try: channel = cmd[1] @@ -1708,7 +1837,7 @@ class Defender(): self.Logs.warning(f'_hcmd BAN: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') - case 'unban' | 'ub': + case 'unban': # .unban #channel nickname try: channel = cmd[1] @@ -1720,7 +1849,7 @@ class Defender(): self.Logs.warning(f'_hcmd UNBAN: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]') - case 'kick' | 'k': + case 'kick': # .kick #channel nickname reason try: channel = cmd[1] @@ -1738,7 +1867,7 @@ class Defender(): self.Logs.warning(f'_hcmd KICK: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]') - case 'kickban' | 'kb': + case 'kickban': # .kickban #channel nickname reason try: channel = cmd[1] diff --git a/version.json b/version.json index 7cad28f..ea705b3 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "5.0.4" + "version": "5.0.5" } \ No newline at end of file