diff --git a/core/base.py b/core/base.py index 5805bb7..e960b4f 100644 --- a/core/base.py +++ b/core/base.py @@ -1,8 +1,9 @@ -import time, threading, os, random, socket, hashlib +import time, threading, os, random, socket, hashlib, ipaddress, logging, requests, json, sys from datetime import datetime from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy.sql import text from core.configuration import Config +from core.sys_configuration import SysConfig class Base: @@ -19,6 +20,10 @@ class Base: def __init__(self, Config: Config) -> None: self.Config = Config # Assigner l'objet de configuration + self.SysConfig = SysConfig() # Importer les information pour le systeme + self.init_log_system() # Demarrer le systeme de log + + self.get_latest_defender_version() self.running_timers:list[threading.Timer] = [] # Liste des timers en cours self.running_threads:list[threading.Thread] = [] # Liste des threads en cours @@ -32,7 +37,27 @@ class Base: self.db_create_first_admin() # Créer un nouvel admin si la base de données est vide - def get_unixtime(self)->int: + def get_latest_defender_version(self) -> None: + try: + #token = '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' + token = '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' + json_url = f'https://github.com/adator85/IRC_DEFENDER_MODULES/blob/e27a027ae99a6c11171635b2a120803e8682aac6/version.json' + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3.raw' # Indique à GitHub que nous voulons le contenu brut du fichier + } + response = requests.get(json_url) + response.raise_for_status() # Vérifie si la requête a réussi + json_response:dict = response.json() + self.SysConfig.LATEST_DEFENDER_VERSION = json_response["version"] + + return None + except requests.HTTPError as err: + self.logs.error(f'Github not available to fetch latest version: {err}') + except: + self.logs.warning(f'Github not available to fetch latest version') + + def get_unixtime(self) -> int: """ Cette fonction retourne un UNIXTIME de type 12365456 Return: Current time in seconds since the Epoch (int) @@ -40,7 +65,7 @@ class Base: unixtime = int( time.time() ) return unixtime - def get_datetime(self)->str: + def get_datetime(self) -> str: """ Retourne une date au format string (24-12-2023 20:50:59) """ @@ -62,12 +87,36 @@ class Base: return None + def init_log_system(self) -> None: + # Create folder if not available + logs_directory = f'logs{os.sep}' + if not os.path.exists(f'{logs_directory}'): + os.makedirs(logs_directory) + + # Init logs object + self.logs = logging + self.logs.basicConfig(level=self.Config.DEBUG_LEVEL, + filename='logs/defender.log', + encoding='UTF-8', + format='%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s') + + self.logs.info('#################### STARTING INTERCEPTOR HQ ####################') + + 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 """ + cmd_list = cmd.split() + if len(cmd_list) == 3: + if cmd_list[0].replace('.', '') == 'auth': + cmd_list[1] = '*******' + cmd_list[2] = '*******' + cmd = ' '.join(cmd_list) + 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) @@ -100,13 +149,13 @@ class Base: """ if not self.db_isModuleExist(module_name): - self.__debug(f"Le module {module_name} n'existe pas alors ont le créer") + self.logs.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") + self.logs.debug(f"Le module {module_name} existe déja dans la base de données") return False @@ -137,7 +186,7 @@ class Base: (:createdOn, :user, :password, :hostname, :vhost, :level)""" , mes_donnees) - pass + return None def create_timer(self, time_to_wait: float, func: object, func_args: tuple = ()) -> None: @@ -148,14 +197,20 @@ class Base: self.running_timers.append(t) - self.__debug(f"Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") + self.logs.debug(f"Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") except AssertionError as ae: - self.__debug(f'Assertion Error -> {ae}') + self.logs.error(f'Assertion Error -> {ae}') - def create_thread(self, func:object, func_args: tuple = ()) -> None: + def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False) -> None: try: func_name = func.__name__ + + if run_once: + for thread in self.running_threads: + if thread.getName() == func_name: + return None + # if func_name in self.running_threads: # print(f"HeartBeat is running") # return None @@ -164,98 +219,95 @@ class Base: th.start() self.running_threads.append(th) - self.__debug(f"Thread ID : {str(th.ident)} | Thread name : {th.getName()} | Running Threads : {len(threading.enumerate())}") + self.logs.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}') + self.logs.error(f'{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") + self.logs.info(f"Timer {str(timer)} removed") else: - self.__debug(f"===> Timer {str(timer)} Still running ...") + self.logs.debug(f"===> Timer {str(timer)} Still running ...") except AssertionError as ae: - print(f'Assertion Error -> {ae}') + self.logs.error(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") + self.logs.debug(f"Thread {str(thread.getName())} {str(thread.native_id)} removed") # print(threading.enumerate()) except AssertionError as ae: - self.__debug(f'Assertion Error -> {ae}') + self.logs.error(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()) + self.logs.debug(soc.fileno()) soc.close() soc.close() self.running_sockets.remove(soc) - self.__debug(f"Socket ==> closed {str(soc.fileno())}") + self.logs.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") + self.logs.debug(f"=======> Checking for Timers to stop") for timer in self.running_timers: while timer.is_alive(): - print(f"> waiting for {timer.getName()} to close") + self.logs.debug(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}") + self.logs.debug(f"> Cancelling {timer.getName()} {timer.native_id}") - print(f"=======> Checking for Threads to stop") + self.logs.debug(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.logs.debug(f"> Running the last periodic action") self.running_threads.remove(thread) - print(f"> Cancelling {thread.getName()} {thread.native_id}") + self.logs.debug(f"> Cancelling {thread.getName()} {thread.native_id}") - print(f"=======> Checking for Sockets to stop") + self.logs.debug(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())}") + self.logs.debug(f"> Socket ==> closed {str(soc.fileno())}") - pass + return None 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 + db_directory = self.SysConfig.DEFENDER_DB_PATH + full_path_db = self.SysConfig.DEFENDER_DB_PATH + self.SysConfig.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() - + self.logs.info("database connexion has been initiated") return engine, cursor def __create_db(self) -> None: @@ -319,7 +371,7 @@ class Base: try: self.cursor.close() except AttributeError as ae: - self.__debug(f"Attribute Error : {ae}") + self.logs.error(f"Attribute Error : {ae}") def crypt_password(self, password:str) -> str: """Retourne un mot de passe chiffré en MD5 @@ -352,6 +404,17 @@ class Base: except TypeError: return value + def is_valid_ip(self, ip_to_control:str) -> bool: + + try: + if ip_to_control in self.Config.WHITELISTED_IP: + return False + + ipaddress.ip_address(ip_to_control) + return True + except ValueError: + return False + def get_random(self, lenght:int) -> str: """ Retourn une chaîne aléatoire en fonction de la longueur spécifiée. @@ -367,7 +430,7 @@ class Base: # Run Garbage Collector Timer self.garbage_collector_timer() self.garbage_collector_thread() - self.garbage_collector_sockets() + # self.garbage_collector_sockets() return None for key, value in self.periodic_func.items(): @@ -379,10 +442,3 @@ class Base: # 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 \ No newline at end of file diff --git a/core/exemple_configuration.py b/core/exemple_configuration.py index 99d3ea4..5298de8 100644 --- a/core/exemple_configuration.py +++ b/core/exemple_configuration.py @@ -1,4 +1,3 @@ -import os ########################################## # CONFIGURATION FILE : # # Rename file to : configuration.py # @@ -6,18 +5,15 @@ import os class Config: - DEFENDER_VERSION = '3.2.2' # 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_IP = '0.0.0.0' # 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 + SERVEUR_SSL = True # Activer / Desactiver la connexion SSL + SERVICE_NAME = 'defender' # Le nom du service SERVICE_NICKNAME = 'BotName' # Nick du bot sur IRC SERVICE_REALNAME = 'BotRealname' # Realname du bot SERVICE_USERNAME = 'BotIdent' # Ident du bot @@ -37,9 +33,13 @@ class Config: 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 + API_TIMEOUT = 2 # Timeout des api's - DEBUG = 0 # Afficher l'ensemble des messages du serveurs dans la console + PORTS_TO_SCAN = [3028, 8080, 1080, 1085, 4145, 9050] # Liste des ports a scanné pour une detection de proxy + WHITELISTED_IP = ['127.0.0.1'] # IP a ne pas scanner + GLINE_DURATION = '1d' # La durée du gline + + DEBUG_LEVEL = 10 # Le niveau des logs DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50 CONFIG_COLOR = { 'blanche': '\x0300', # Couleur blanche diff --git a/core/installation.py b/core/installation.py index ac0d957..8eb709b 100644 --- a/core/installation.py +++ b/core/installation.py @@ -37,7 +37,7 @@ class Install: print(f"===> Version of python : {python_version()} ==> OK") return True - + def checkDependencies(self) -> None: """### Verifie les dépendances si elles sont installées - Test si les modules sont installés diff --git a/core/irc.py b/core/irc.py index 545cc31..b1e9082 100644 --- a/core/irc.py +++ b/core/irc.py @@ -1,7 +1,9 @@ import ssl, re, importlib, sys, time, threading, socket +from ssl import SSLSocket from datetime import datetime, timedelta - +from typing import Union from core.configuration import Config +from core.sys_configuration import SysConfig from core.base import Base class Irc: @@ -16,12 +18,15 @@ class Irc: self.beat = 30 # Lancer toutes les 30 secondes des actions de nettoyages self.hb_active = True # Heartbeat active self.HSID = '' # ID du serveur qui accueil le service ( Host Serveur Id ) + self.IrcSocket:Union[socket.socket, SSLSocket] = None self.INIT = 1 # Variable d'intialisation | 1 -> indique si le programme est en cours d'initialisation self.RESTART = 0 # Variable pour le redemarrage du bot | 0 -> indique que le programme n'es pas en cours de redemarrage - self.CHARSET = ['utf-8', 'iso-8859-1'] # Charset utiliser pour décoder/encoder les messages + self.CHARSET = ['utf-8', 'iso-8859-1'] # Charset utiliser pour décoder/encoder les messages + self.SSL_VERSION = None # Version SSL self.Config = Config() + self.SysConfig = SysConfig() # Liste des commandes internes du bot self.commands_level = { @@ -38,7 +43,7 @@ class Irc: self.commands.append(command) self.Base = Base(self.Config) - self.Base.create_thread(self.heartbeat, (self.beat, )) + self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, )) ############################################## # CONNEXION IRC # @@ -48,26 +53,46 @@ class Irc: self.__create_socket() self.__connect_to_irc(ircInstance) except AssertionError as ae: - self.debug(f'Assertion error : {ae}') + self.Base.logs.critical(f'Assertion error: {ae}') def __create_socket(self) -> None: - self.IrcSocket: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - connexion_information = (self.Config.SERVEUR_IP, self.Config.SERVEUR_PORT) - self.IrcSocket.connect(connexion_information) + try: + soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) + connexion_information = (self.Config.SERVEUR_IP, self.Config.SERVEUR_PORT) - # Créer un object ssl - ssl_context = self.__ssl_context() - ssl_connexion = ssl_context.wrap_socket(self.IrcSocket, server_hostname=self.Config.SERVEUR_HOSTNAME) - self.IrcSocket = ssl_connexion + if self.Config.SERVEUR_SSL: + # Créer un object ssl + ssl_context = self.__ssl_context() + ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=self.Config.SERVEUR_HOSTNAME) + ssl_connexion.connect(connexion_information) + self.IrcSocket:SSLSocket = ssl_connexion - return None + self.Base.logs.info(f"Connexion en mode SSL : Version = {self.IrcSocket.version()}") + self.SSL_VERSION = self.IrcSocket.version() + else: + soc.connect(connexion_information) + self.IrcSocket:socket.socket = soc + self.Base.logs.info("Connexion en mode normal") + + return None + + except ssl.SSLEOFError as soe: + self.Base.logs.critical(f"SSLEOFError __create_socket: {soe} - {soc.fileno()}") + except ssl.SSLError as se: + self.Base.logs.critical(f"SSLError __create_socket: {se} - {soc.fileno()}") + except OSError as oe: + self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}") + except AttributeError as ae: + self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}") def __ssl_context(self) -> ssl.SSLContext: - ctx = ssl.create_default_context() + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE + self.Base.logs.debug(f'SSLContext initiated with verified mode {ctx.verify_mode}') + return ctx def __connect_to_irc(self, ircInstance: 'Irc') -> None: @@ -78,50 +103,61 @@ class Irc: self.load_existing_modules() # Charger les modules existant dans la base de données while self.signal: - if self.RESTART == 1: - self.IrcSocket.shutdown(socket.SHUT_RDWR) - self.IrcSocket.close() + try: + if self.RESTART == 1: + self.Base.logs.debug('Restarting Defender ...') + self.IrcSocket.shutdown(socket.SHUT_RDWR) + self.IrcSocket.close() - while self.IrcSocket.fileno() != -1: - time.sleep(0.5) - self.debug("--> En attente de la fermeture du socket ...") + while self.IrcSocket.fileno() != -1: + time.sleep(0.5) + self.Base.logs.warning('--> Waiting for socket to close ...') - self.__create_socket() - self.__link(self.IrcSocket) - self.load_existing_modules() - self.RESTART = 0 - # 4072 max what the socket can grab - buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) - # data = self.IrcSocket.recv(buffer_size).splitlines(True) + self.__create_socket() + self.__link(self.IrcSocket) + self.load_existing_modules() + self.RESTART = 0 - data_in_bytes = self.IrcSocket.recv(buffer_size) - count_bytes = len(data_in_bytes) + # 4072 max what the socket can grab + buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) + data_in_bytes = self.IrcSocket.recv(buffer_size) + data = data_in_bytes.splitlines(True) + count_bytes = len(data_in_bytes) - while count_bytes > 4070: - # If the received message is > 4070 then loop and add the value to the variable - new_data = self.IrcSocket.recv(buffer_size) - data_in_bytes += new_data - count_bytes = len(new_data) - # print("========================================================") + while count_bytes > 4070: + # If the received message is > 4070 then loop and add the value to the variable + new_data = self.IrcSocket.recv(buffer_size) + data_in_bytes += new_data + count_bytes = len(new_data) - data = data_in_bytes.splitlines() + data = data_in_bytes.splitlines(True) - # print(f"{str(buffer_size)} - {str(len(data_in_bytes))}") + if not data: + break - if not data: - break + self.send_response(data) - self.send_response(data) + except ssl.SSLEOFError as soe: + self.Base.logs.error(f"SSLEOFError __connect_to_irc: {soe} - {data}") + except ssl.SSLError as se: + self.Base.logs.error(f"SSLError __connect_to_irc: {se} - {data}") + except OSError as oe: + self.Base.logs.error(f"SSLError __connect_to_irc: {oe} - {data}") self.IrcSocket.shutdown(socket.SHUT_RDWR) self.IrcSocket.close() + self.Base.logs.info("--> Fermeture de Defender ...") except AssertionError as ae: - self.debug(f'Assertion error : {ae}') + self.Base.logs.error(f'Assertion error : {ae}') except ValueError as ve: - self.debug(f'Value Error : {ve}') - except OSError as oe: - self.debug(f"OS Error : {oe}") + self.Base.logs.error(f'Value Error : {ve}') + except ssl.SSLEOFError as soe: + self.Base.logs.error(f"OS Error __connect_to_irc: {soe}") + except AttributeError as atte: + self.Base.logs.critical(f"{atte}") + # except Exception as e: + # self.debug(f"Exception: {e}") def __link(self, writer:socket.socket) -> None: """Créer le link et envoyer les informations nécessaires pour la @@ -130,63 +166,70 @@ class Irc: Args: writer (StreamWriter): permet l'envoi des informations au serveur. """ + try: + nickname = self.Config.SERVICE_NICKNAME + username = self.Config.SERVICE_USERNAME + realname = self.Config.SERVICE_REALNAME + chan = self.Config.SERVICE_CHANLOG + info = self.Config.SERVICE_INFO + smodes = self.Config.SERVICE_SMODES + cmodes = self.Config.SERVICE_CMODES + umodes = self.Config.SERVICE_UMODES + host = self.Config.SERVICE_HOST + service_name = self.Config.SERVICE_NAME - nickname = self.Config.SERVICE_NICKNAME - username = self.Config.SERVICE_USERNAME - realname = self.Config.SERVICE_REALNAME - chan = self.Config.SERVICE_CHANLOG - info = self.Config.SERVICE_INFO - smodes = self.Config.SERVICE_SMODES - cmodes = self.Config.SERVICE_CMODES - umodes = self.Config.SERVICE_UMODES - host = self.Config.SERVICE_HOST - service_name = self.Config.SERVICE_NAME + password = self.Config.SERVEUR_PASSWORD + link = self.Config.SERVEUR_LINK + sid = self.Config.SERVEUR_ID + service_id = self.Config.SERVICE_ID - password = self.Config.SERVEUR_PASSWORD - link = self.Config.SERVEUR_LINK - sid = self.Config.SERVEUR_ID - service_id = self.Config.SERVICE_ID + version = self.SysConfig.DEFENDER_VERSION + unixtime = self.Base.get_unixtime() - version = self.Config.DEFENDER_VERSION - unixtime = self.Base.get_unixtime() + # Envoyer un message d'identification + writer.send(f":{sid} PASS :{password}\r\n".encode('utf-8')) + writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode('utf-8')) + writer.send(f":{sid} PROTOCTL EAUTH={link},,,{service_name}-v{version}\r\n".encode('utf-8')) + writer.send(f":{sid} PROTOCTL SID={sid}\r\n".encode('utf-8')) + writer.send(f":{sid} SERVER {link} 1 :{info}\r\n".encode('utf-8')) + writer.send(f":{sid} {nickname} :Reserved for services\r\n".encode('utf-8')) + writer.send(f":{sid} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * * :{realname}\r\n".encode('utf-8')) + writer.send(f":{sid} SJOIN {unixtime} {chan} + :{service_id}\r\n".encode('utf-8')) + writer.send(f":{sid} MODE {chan} +{cmodes}\r\n".encode('utf-8')) + writer.send(f":{service_id} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8')) - # Envoyer un message d'identification - # strtobytes = bytes(":" + sid + " PASS :" + password + "\r\n", 'utf-8') - # self.IrcSocket.send(strtobytes) - writer.send(f":{sid} PASS :{password}\r\n".encode('utf-8')) - writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode('utf-8')) - writer.send(f":{sid} PROTOCTL EAUTH={link},,,{service_name}-v{version}\r\n".encode('utf-8')) - writer.send(f":{sid} PROTOCTL SID={sid}\r\n".encode('utf-8')) - writer.send(f":{sid} SERVER {link} 1 :{info}\r\n".encode('utf-8')) - writer.send(f":{sid} {nickname} :Reserved for services\r\n".encode('utf-8')) - writer.send(f":{sid} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * * :{realname}\r\n".encode('utf-8')) - writer.send(f":{sid} SJOIN {unixtime} {chan} + :{service_id}\r\n".encode('utf-8')) - writer.send(f":{sid} MODE {chan} +{cmodes}\r\n".encode('utf-8')) - writer.send(f":{service_id} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8')) - - # writer.write(f"USER {nickname} {username} {username} {nickname} {username} :{username}\r\n".encode('utf-8')) - # writer.write(f"USER {username} {username} {username} :{username}\r\n".encode('utf-8')) - # writer.write(f"NICK {nickname}\r\n".encode('utf-8')) + self.Base.logs.debug('Link information sent to the server') - def send2socket(self, send_message:str)->None: + return None + except AttributeError as ae: + self.Base.logs.critical(f'{ae}') + + def send2socket(self, send_message:str) -> None: """Envoit les commandes à envoyer au serveur. Args: string (Str): contient la commande à envoyer au serveur. """ try: - self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0])) + with self.Base.lock: + # print(f">{str(send_message)}") + self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0])) + self.Base.logs.debug(f'{send_message}') except UnicodeDecodeError: - self.debug('Write Decode impossible try iso-8859-1') + self.Base.logs.error(f'Decode Error try iso-8859-1 - message: {send_message}') self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0],'replace')) except UnicodeEncodeError: - self.debug('Write Encode impossible ... try iso-8859-1') + self.Base.logs.error(f'Encode Error try iso-8859-1 - message: {send_message}') self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0],'replace')) except AssertionError as ae: - self.debug(f"Assertion error : {ae}") + self.Base.logs.warning(f'Assertion Error {ae} - message: {send_message}') + except ssl.SSLEOFError as soe: + self.Base.logs.error(f"SSLEOFError: {soe} - {send_message}") + except ssl.SSLError as se: + self.Base.logs.error(f"SSLError: {se} - {send_message}") except OSError as oe: - self.debug(f"OS Error : {oe}") + self.Base.logs.error(f"OSError: {oe} - {send_message}") def send_response(self, responses:list[bytes]) -> None: try: @@ -194,6 +237,7 @@ class Irc: for data in responses: response = data.decode(self.CHARSET[0]).split() self.cmd(response) + except UnicodeEncodeError: for data in responses: response = data.decode(self.CHARSET[1],'replace').split() @@ -203,10 +247,12 @@ class Irc: response = data.decode(self.CHARSET[1],'replace').split() self.cmd(response) except AssertionError as ae: - self.debug(f"Assertion error : {ae}") + self.Base.logs.error(f"Assertion error : {ae}") + ############################################## # FIN CONNEXION IRC # ############################################## + def load_existing_modules(self) -> None: """Charge les modules qui existe déja dans la base de données @@ -219,7 +265,7 @@ class Irc: return None - def get_defender_uptime(self)->str: + def get_defender_uptime(self) -> str: """Savoir depuis quand Defender est connecté Returns: @@ -228,7 +274,7 @@ class Irc: current_datetime = datetime.now() diff_date = current_datetime - self.defender_connexion_datetime uptime = timedelta(days=diff_date.days, seconds=diff_date.seconds) - + return uptime def heartbeat(self, beat:float) -> None: @@ -252,7 +298,7 @@ class Irc: # 2. Executer la fonction try: if not class_name in self.loaded_classes: - self.debug(f"La class [{class_name} n'existe pas !!]") + self.Base.logs.error(f"La class [{class_name} n'existe pas !!]") return False class_instance = self.loaded_classes[class_name] @@ -262,12 +308,12 @@ class Irc: self.Base.running_timers.append(t) - self.debug(f"Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") + self.Base.logs.debug(f"Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") except AssertionError as ae: - self.debug(f'Assertion Error -> {ae}') + self.Base.logs.error(f'Assertion Error -> {ae}') except TypeError as te: - self.debug(f"Type error -> {te}") + self.Base.logs.error(f"Type error -> {te}") def __create_tasks(self, obj: object, method_name: str, param:list) -> None: """#### Ajouter les méthodes a éxecuter dans un dictionnaire @@ -286,7 +332,7 @@ class Irc: 'param': param } - self.debug(f'Function to execute : {str(self.Base.periodic_func)}') + self.Base.logs.debug(f'Function to execute : {str(self.Base.periodic_func)}') self.send_ping_to_sereur() return None @@ -300,7 +346,6 @@ class Irc: return None def load_module(self, fromuser:str, module_name:str, init:bool = False) -> bool: - try: # module_name : mod_voice module_name = module_name.lower() @@ -310,8 +355,8 @@ class Irc: # Si le module est déja chargé if 'mods.' + module_name in sys.modules: - self.debug("Module déja chargé ...") - self.debug('module name = ' + module_name) + self.Base.logs.info("Module déja chargé ...") + self.Base.logs.info('module name = ' + module_name) if class_name in self.loaded_classes: # Si le module existe dans la variable globale retourne False self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Le module {module_name} est déja chargé ! si vous souhaiter le recharge tapez {self.Config.SERVICE_PREFIX}reload {module_name}") @@ -342,14 +387,14 @@ class Irc: self.Base.db_record_module(fromuser, module_name) self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Module {module_name} chargé") - self.debug(self.loaded_classes) + self.Base.logs.info(self.loaded_classes) return True except ModuleNotFoundError as moduleNotFound: - self.debug(f"MODULE_NOT_FOUND: {moduleNotFound}") + self.Base.logs.error(f"MODULE_NOT_FOUND: {moduleNotFound}") self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :[ {self.Config.CONFIG_COLOR['rouge']}MODULE_NOT_FOUND{self.Config.CONFIG_COLOR['noire']} ]: {moduleNotFound}") - except: - self.debug(f"Something went wrong with a module you want to load") + except Exception as e: + self.Base.logs.error(f"Something went wrong with a module you want to load : {e}") def insert_db_uid(self, uid:str, nickname:str, username:str, hostname:str, umodes:str, vhost:str, isWebirc: bool) -> None: @@ -379,7 +424,7 @@ class Irc: return None def update_db_uid(self, uid:str, newnickname:str) -> None: - + # Récupérer l'ancien nickname oldnickname = self.db_uid[uid]['nickname'] @@ -391,7 +436,7 @@ class Irc: 'umodes': self.db_uid[uid]['umodes'], 'vhost': self.db_uid[uid]['vhost'] } - + # Modification du nickname dans la ligne UID self.db_uid[uid]['nickname'] = newnickname @@ -399,11 +444,11 @@ class Irc: if oldnickname in self.db_uid: del self.db_uid[oldnickname] else: - self.debug(f"L'ancien nickname {oldnickname} n'existe pas dans UID_DB") + self.Base.logs.debug(f"L'ancien nickname {oldnickname} n'existe pas dans UID_DB") response = False - self.debug(f"{oldnickname} changed to {newnickname}") - + self.Base.logs.debug(f"{oldnickname} changed to {newnickname}") + return None def delete_db_uid(self, uid:str) -> None: @@ -430,8 +475,6 @@ class Irc: umodes = self.db_uid[uid]['umodes'] vhost = self.db_uid[uid]['vhost'] level = int(level) - - self.db_admin[uid] = { 'nickname': nickname, @@ -481,7 +524,7 @@ class Irc: """ if channel in self.db_chan: return False - + response = True # Ajouter un nouveau salon self.db_chan.append(channel) @@ -489,7 +532,7 @@ class Irc: # Supprimer les doublons de la liste self.db_chan = list(set(self.db_chan)) - self.debug(f"Le salon {channel} a été ajouté à la liste CHAN_DB") + self.Base.logs.debug(f"Le salon {channel} a été ajouté à la liste CHAN_DB") return response @@ -500,13 +543,13 @@ class Irc: if level > 4: response = "Impossible d'ajouter un niveau > 4" - self.debug(response) + self.Base.logs.warning(response) return response # Verification si le user existe dans notre UID_DB if not nickname in self.db_uid: response = f"{nickname} n'est pas connecté, impossible de l'enregistrer pour le moment" - self.debug(response) + self.Base.logs.warning(response) return response hostname = self.db_uid[nickname]['hostname'] @@ -527,15 +570,15 @@ class Irc: ''', mes_donnees) response = f"{nickname} ajouté en tant qu'administrateur de niveau {level}" self.send2socket(f':{self.Config.SERVICE_NICKNAME} NOTICE {nickname} : {response}') - self.debug(response) + self.Base.logs.info(response) return response else: response = f'{nickname} Existe déjà dans les users enregistrés' self.send2socket(f':{self.Config.SERVICE_NICKNAME} NOTICE {nickname} : {response}') - self.debug(response) + self.Base.logs.info(response) return response - def get_uid(self, uidornickname:str) -> str | None: + def get_uid(self, uidornickname:str) -> Union[str, None]: uid_recherche = uidornickname response = None @@ -548,8 +591,8 @@ class Irc: return response - def get_nickname(self, uidornickname:str) -> str | None: - + def get_nickname(self, uidornickname:str) -> Union[str, None]: + nickname_recherche = uidornickname response = None @@ -563,11 +606,11 @@ class Irc: return response def is_cmd_allowed(self,nickname:str, cmd:str) -> bool: - + # Vérifier si le user est identifié et si il a les droits is_command_allowed = False uid = self.get_uid(nickname) - + if uid in self.db_admin: admin_level = self.db_admin[uid]['level'] @@ -606,13 +649,27 @@ class Irc: def cmd(self, data:list) -> None: try: + cmd_to_send:list[str] = data.copy() cmd = data.copy() + cmd_to_debug = data.copy() + cmd_to_debug.pop(0) + if len(cmd) == 0 or len(cmd) == 1: + self.Base.logs.warning(f'Size ({str(len(cmd))}) - {cmd}') return False - self.debug(cmd) + # self.debug(cmd_to_debug) + if len(data) == 7: + if data[2] == 'PRIVMSG' and data[4] == ':auth': + data_copy = data.copy() + data_copy[6] = '**********' + self.Base.logs.debug(data_copy) + else: + self.Base.logs.debug(data) + else: + self.Base.logs.debug(data) match cmd[0]: @@ -654,8 +711,8 @@ class Irc: # self.Base.create_thread(self.abuseipdb_scan, (cmd[2], )) pass # Possibilité de déclancher les bans a ce niveau. - except IndexError: - self.debug(f'cmd reputation: index error') + except IndexError as ie: + self.Base.logs.error(f'{ie}') case '320': #:irc.deb.biz.st 320 PyDefender IRCParis07 :is in security-groups: known-users,webirc-users,tls-and-known-users,tls-users @@ -680,11 +737,24 @@ class Irc: print(f"# SERVICE CONNECTE ") print(f"# SERVEUR : {self.Config.SERVEUR_IP} ") print(f"# PORT : {self.Config.SERVEUR_PORT} ") + print(f"# SSL : {self.Config.SERVEUR_SSL} ") + print(f"# SSL VER : {self.SSL_VERSION} ") print(f"# NICKNAME : {self.Config.SERVICE_NICKNAME} ") print(f"# CHANNEL : {self.Config.SERVICE_CHANLOG} ") - print(f"# VERSION : {self.Config.DEFENDER_VERSION} ") + print(f"# VERSION : {self.SysConfig.DEFENDER_VERSION} ") print(f"################################################") + self.Base.logs.info(f"################### DEFENDER ###################") + self.Base.logs.info(f"# SERVICE CONNECTE ") + self.Base.logs.info(f"# SERVEUR : {self.Config.SERVEUR_IP} ") + self.Base.logs.info(f"# PORT : {self.Config.SERVEUR_PORT} ") + self.Base.logs.info(f"# SSL : {self.Config.SERVEUR_SSL} ") + self.Base.logs.info(f"# SSL VER : {self.SSL_VERSION} ") + self.Base.logs.info(f"# NICKNAME : {self.Config.SERVICE_NICKNAME} ") + self.Base.logs.info(f"# CHANNEL : {self.Config.SERVICE_CHANLOG} ") + self.Base.logs.info(f"# VERSION : {self.SysConfig.DEFENDER_VERSION} ") + self.Base.logs.info(f"################################################") + # Initialisation terminé aprés le premier PING self.INIT = 0 # self.send2socket(f':{self.Config.SERVICE_ID} PING :{hsid}') @@ -703,7 +773,7 @@ class Irc: cmd.pop(0) uid_who_quit = str(cmd[0]).replace(':', '') self.delete_db_uid(uid_who_quit) - + case 'PONG': # ['@msgid=aTNJhp17kcPboF5diQqkUL;time=2023-12-28T20:35:58.411Z', ':irc.deb.biz.st', 'PONG', 'irc.deb.biz.st', ':Dev-PyDefender'] self.Base.execute_periodic_action() @@ -750,6 +820,15 @@ class Irc: cmd.pop(0) get_uid_or_nickname = str(cmd[0].replace(':','')) + if len(cmd) == 6: + if cmd[1] == 'PRIVMSG' and cmd[3] == ':auth': + cmd_copy = cmd.copy() + cmd_copy[5] = '**********' + self.Base.logs.debug(cmd_copy) + else: + self.Base.logs.info(cmd) + else: + self.Base.logs.info(f'{cmd}') # user_trigger = get_user.split('!')[0] user_trigger = self.get_nickname(get_uid_or_nickname) dnickname = self.Config.SERVICE_NICKNAME @@ -783,15 +862,15 @@ class Irc: # Réponse a un CTCP VERSION if arg[0] == '\x01VERSION\x01': - self.send2socket(f':{dnickname} NOTICE {user_trigger} :\x01VERSION Service {self.Config.SERVICE_NICKNAME} V{self.Config.DEFENDER_VERSION}\x01') + self.send2socket(f':{dnickname} NOTICE {user_trigger} :\x01VERSION Service {self.Config.SERVICE_NICKNAME} V{self.SysConfig.DEFENDER_VERSION}\x01') return False - + # Réponse a un TIME if arg[0] == '\x01TIME\x01': current_datetime = self.Base.get_datetime() self.send2socket(f':{dnickname} NOTICE {user_trigger} :\x01TIME {current_datetime}\x01') return False - + # Réponse a un PING if arg[0] == '\x01PING': recieved_unixtime = int(arg[1].replace('\x01','')) @@ -809,8 +888,8 @@ class Irc: self._hcmds(user_trigger, arg) - except IndexError: - self.debug(f'cmd --> PRIVMSG --> List index out of range') + except IndexError as io: + self.Base.logs.error(f'{io}') case _: pass @@ -821,7 +900,7 @@ class Irc: classe_object.cmd(cmd_to_send) except IndexError as ie: - self.debug(f"IRC CMD -> IndexError : {ie} - {cmd} - length {str(len(cmd))}") + self.Base.logs.error(f"{ie} / {cmd} / length {str(len(cmd))}") def _hcmds(self, user: str, cmd:list) -> None: @@ -853,8 +932,8 @@ class Irc: current_command = cmd[0] self.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["rouge"]}{current_command}{self.Config.CONFIG_COLOR["noire"]} ] - Accès Refusé à {self.get_nickname(fromuser)}') self.send2socket(f':{dnickname} NOTICE {fromuser} : Accès Refusé') - except IndexError: - self.debug(f'_hcmd notallowed : Index Error') + except IndexError as ie: + self.Base.logs.error(f'{ie}') case 'deauth': @@ -874,11 +953,12 @@ class Irc: query = f"SELECT id, level FROM {self.Base.DB_SCHEMA['admins']} WHERE user = :user AND password = :password" result = self.Base.db_execute_query(query, mes_donnees) user_from_db = result.fetchone() - + if not user_from_db is None: uid_user = self.get_uid(user_to_log) self.insert_db_admin(uid_user, user_from_db[1]) self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}{current_command}{self.Config.CONFIG_COLOR['noire']} ] - {self.get_nickname(fromuser)} est désormais connecté a {dnickname}") + self.send2socket(f":{self.Config.SERVICE_NICKNAME} NOTICE {fromuser} :Connexion a {dnickname} réussie!") else: self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['rouge']}{current_command}{self.Config.CONFIG_COLOR['noire']} ] - {self.get_nickname(fromuser)} a tapé un mauvais mot de pass") self.send2socket(f":{self.Config.SERVICE_NICKNAME} NOTICE {fromuser} :Mot de passe incorrecte") @@ -895,13 +975,13 @@ class Irc: response = self.create_defender_user(newnickname, newlevel, password) self.send2socket(f':{dnickname} NOTICE {fromuser} : {response}') - self.debug(response) + self.Base.logs.info(response) except IndexError as ie: - self.debug(f'_hcmd addaccess: {ie}') + self.Base.logs.error(f'_hcmd addaccess: {ie}') self.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} addaccess [nickname] [level] [password]') except TypeError as te: - self.debug(f'_hcmd addaccess: out of index : {te}') + self.Base.logs.error(f'_hcmd addaccess: out of index : {te}') self.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} addaccess [nickname] [level] [password]') case 'editaccess': @@ -949,9 +1029,9 @@ class Irc: self.send2socket(f":{dnickname} NOTICE {fromuser} : Impossible de modifier l'utilisateur {str(user_new_level)}") except TypeError as te: - self.debug(f"Type error : {te}") + self.Base.logs.error(f"Type error : {te}") except ValueError as ve: - self.debug(f"Value Error : {ve}") + self.Base.logs.error(f"Value Error : {ve}") self.send2socket(f':{dnickname} NOTICE {fromuser} : .editaccess [USER] [NEWPASSWORD] [NEWLEVEL]') case 'delaccess': @@ -961,9 +1041,9 @@ class Irc: if user_to_del != user_confirmation: self.send2socket(f':{dnickname} NOTICE {fromuser} : Les user ne sont pas les mêmes, tu dois confirmer le user que tu veux supprimer') + self.Base.logs.warning(f':{dnickname} NOTICE {fromuser} : Les user ne sont pas les mêmes, tu dois confirmer le user que tu veux supprimer') return None - print(len(cmd)) if len(cmd) < 3: self.send2socket(f':{dnickname} NOTICE {fromuser} : .delaccess [USER] [CONFIRMUSER]') return None @@ -982,6 +1062,7 @@ class Irc: level_user_to_del = info_user[1] if current_user_level <= level_user_to_del: self.send2socket(f':{dnickname} NOTICE {fromuser} : You are not allowed to delete this access') + self.Base.logs.warning(f':{dnickname} NOTICE {fromuser} : You are not allowed to delete this access') return None data_to_delete = {'user': user_to_del} @@ -991,6 +1072,7 @@ class Irc: self.send2socket(f':{dnickname} NOTICE {fromuser} : User {user_to_del} has been deleted !') else: self.send2socket(f":{dnickname} NOTICE {fromuser} : Impossible de supprimer l'utilisateur.") + self.Base.logs.warning(f":{dnickname} NOTICE {fromuser} : Impossible de supprimer l'utilisateur.") case 'help': @@ -1034,7 +1116,7 @@ class Irc: class_name = module_name.split('_')[1].capitalize() # Nom de la class. exemple: Defender if class_name in self.loaded_classes: - + self.loaded_classes[class_name].unload() for level, command in self.loaded_classes[class_name].commands_level.items(): # Supprimer la commande de la variable commands for c in self.loaded_classes[class_name].commands_level[level]: @@ -1048,7 +1130,7 @@ class Irc: self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Module {module_name} supprimé") except: - self.debug(f"Something went wrong with a module you want to load") + self.Base.logs.error(f"Something went wrong with a module you want to load") case 'reload': # reload mod_dktmb @@ -1057,7 +1139,8 @@ class Irc: class_name = module_name.split('_')[1].capitalize() # ==> Defender if 'mods.' + module_name in sys.modules: - self.debug('Module Already Loaded ... reload the module ...') + self.loaded_classes[class_name].unload() + self.Base.logs.info('Module Already Loaded ... reload the module ...') the_module = sys.modules['mods.' + module_name] importlib.reload(the_module) @@ -1081,7 +1164,7 @@ class Irc: else: self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Module {module_name} n'est pas chargé !") except: - self.debug(f"Something went wrong with a module you want to reload") + self.Base.logs.error(f"Something went wrong with a module you want to reload") case 'quit': try: @@ -1096,13 +1179,13 @@ class Irc: self.send2socket(f':{dnickname} NOTICE {fromuser} : Arrêt du service {dnickname}') self.send2socket(f':{self.Config.SERVEUR_LINK} SQUIT {self.Config.SERVEUR_LINK} :{final_reason}') - self.debug(f'Arrêt du server {dnickname}') + self.Base.logs.info(f'Arrêt du server {dnickname}') self.RESTART = 0 self.signal = False - except IndexError: - self.debug('_hcmd die: out of index') - + except IndexError as ie: + self.Base.logs.error(f'{ie}') + self.send2socket(f"QUIT Good bye") case 'restart': @@ -1114,16 +1197,19 @@ class Irc: self.db_uid.clear() #Vider UID_DB self.db_chan = [] #Vider les salons + for class_name in self.loaded_classes: + self.loaded_classes[class_name].unload() + self.send2socket(f':{dnickname} NOTICE {fromuser} : Redémarrage du service {dnickname}') self.send2socket(f':{self.Config.SERVEUR_LINK} SQUIT {self.Config.SERVEUR_LINK} :{final_reason}') - self.debug(f'Redémarrage du server {dnickname}') + self.Base.logs.info(f'Redémarrage du server {dnickname}') self.loaded_classes.clear() self.RESTART = 1 # Set restart status to 1 saying that the service will restart self.INIT = 1 # set init to 1 saying that the service will be re initiated case 'show_modules': - self.debug(self.loaded_classes) + self.Base.logs.debug(self.loaded_classes) results = self.Base.db_execute_query(f'SELECT module FROM {self.Base.DB_SCHEMA["modules"]}') results = results.fetchall() @@ -1134,33 +1220,36 @@ class Irc: for r in results: self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Le module {r[0]} chargé") - self.debug(r[0]) case 'show_timers': if self.Base.running_timers: self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{self.Base.running_timers}") - self.debug(self.Base.running_timers) else: self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Aucun timers en cours d'execution") case 'show_threads': - self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{self.Base.running_threads}") + + running_thread_name:list = [] + for thread in self.Base.running_threads: + running_thread_name.append(f"{thread.getName()} ({thread.is_alive()})") + + self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{str(running_thread_name)}") case 'uptime': uptime = self.get_defender_uptime() self.send2socket(f':{dnickname} NOTICE {fromuser} : {uptime}') case 'copyright': - self.send2socket(f':{dnickname} NOTICE {fromuser} : # Defender V.{self.Config.DEFENDER_VERSION} Developped by adator® and dktmb® #') + self.send2socket(f':{dnickname} NOTICE {fromuser} : # Defender V.{self.SysConfig.DEFENDER_VERSION} Developped by adator® and dktmb® #') case 'sentinel': # .sentinel on activation = str(cmd[1]).lower() service_id = self.Config.SERVICE_ID - + channel_to_dont_quit = [self.Config.SALON_JAIL, dchanlog] - + if activation == 'on': for chan in self.db_chan: if not chan in channel_to_dont_quit: diff --git a/core/sys_configuration.py b/core/sys_configuration.py new file mode 100644 index 0000000..a69841e --- /dev/null +++ b/core/sys_configuration.py @@ -0,0 +1,22 @@ +import os, json + +#################################################################################################### +# DO NOT TOUCH THIS FILE # +#################################################################################################### + +class SysConfig: + + DEFENDER_VERSION = '4.0.0' # MAJOR.MINOR.BATCH + LATEST_DEFENDER_VERSION = '0.0.0' # Latest Version of Defender in git + 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 + + def __init__(self) -> None: + + version_filename = f'.{os.sep}version.json' + with open(version_filename, 'r') as version_data: + self.global_configuration:dict[str, str] = json.load(version_data) + + self.DEFENDER_VERSION = self.global_configuration["version"] + + return None \ No newline at end of file diff --git a/mods/mod_defender.py b/mods/mod_defender.py index 51b9be9..f96416e 100644 --- a/mods/mod_defender.py +++ b/mods/mod_defender.py @@ -1,5 +1,6 @@ from datetime import datetime -import re, socket, psutil, requests, json +from typing import Union +import re, socket, psutil, requests, json, time from core.irc import Irc # Le module crée devra réspecter quelques conditions @@ -9,7 +10,10 @@ from core.irc import Irc # 2 . Récuperer la configuration dans une variable # 3 . Définir et enregistrer les nouvelles commandes # 4 . Créer vos tables, en utilisant toujours le nom des votre classe en minuscule ==> defender_votre-table -# 3. une methode _hcmds(self, user:str, cmd: list) devra toujours etre crée. +# 3. Methode suivantes: +# cmd(self, data:list) +# _hcmds(self, user:str, cmd: list) +# unload(self) class Defender(): @@ -18,8 +22,22 @@ class Defender(): self.Irc = ircInstance # Ajouter l'object mod_irc a la classe ( Obligatoire ) self.Config = ircInstance.Config # Ajouter la configuration a la classe ( Obligatoire ) self.Base = ircInstance.Base # Ajouter l'objet Base au module ( Obligatoire ) + self.timeout = self.Config.API_TIMEOUT # API Timeout - self.Irc.debug(f'Module {self.__class__.__name__} loaded ...') + self.freeipapi_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec freeipapi + self.cloudfilt_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec cloudfilt + self.abuseipdb_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec abuseipdb + self.psutil_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec psutil_scan + self.localscan_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec local_scan + + self.abuseipdb_isRunning:bool = True + self.freeipapi_isRunning:bool = True + self.cloudfilt_isRunning:bool = True + self.psutil_isRunning:bool = True + self.localscan_isRunning:bool = True + self.reputationTimer_isRunning:bool = True + + self.Irc.Base.logs.info(f'Module {self.__class__.__name__} loaded ...') # Créer les nouvelles commandes du module self.commands_level = { @@ -39,9 +57,6 @@ class Defender(): Args: commands (list): Liste des commandes du module - - Returns: - None: Aucun retour attendu """ for level, com in commands.items(): for c in commands[level]: @@ -51,6 +66,24 @@ class Defender(): return None + def unload(self) -> None: + """Cette methode sera executée a chaque désactivation ou + rechargement de module + """ + self.abuseipdb_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec abuseipdb + self.freeipapi_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec freeipapi + self.cloudfilt_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec cloudfilt + self.psutil_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec psutil_scan + self.localscan_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec local_scan + + self.abuseipdb_isRunning:bool = False + self.freeipapi_isRunning:bool = False + self.cloudfilt_isRunning:bool = False + self.psutil_isRunning:bool = False + self.localscan_isRunning:bool = False + self.reputationTimer_isRunning:bool = False + return None + def __create_tables(self) -> None: """Methode qui va créer la base de donnée si elle n'existe pas. Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module @@ -95,7 +128,10 @@ class Defender(): self.db_reputation = {} # Definir la variable qui contiendra la liste des user concerné par la réputation self.flood_system = {} # Variable qui va contenir les users self.reputation_first_connexion = {'ip': '', 'score': -1} # Contient les premieres informations de connexion + # 13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674 self.abuseipdb_key = '13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674' # Laisser vide si aucune clé + # r1gEtjtfgRQjtNBDMxsg + self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg' # Laisser vide si aucune clé # Rejoindre les salons self.join_saved_channels() @@ -109,6 +145,8 @@ class Defender(): 'local_scan': 0, 'psutil_scan': 0, 'abuseipdb_scan': 0, + 'freeipapi_scan': 0, + 'cloudfilt_scan': 0, 'flood': 0, 'flood_message': 5, 'flood_time': 1, @@ -118,6 +156,14 @@ class Defender(): # Syncrhoniser la variable defConfig avec la configuration de la base de données. self.sync_db_configuration() + # Démarrer les threads pour démarrer les api + self.Base.create_thread(func=self.thread_freeipapi_scan) + self.Base.create_thread(func=self.thread_cloudfilt_scan) + self.Base.create_thread(func=self.thread_abuseipdb_scan) + self.Base.create_thread(func=self.thread_local_scan) + self.Base.create_thread(func=self.thread_psutil_scan) + self.Base.create_thread(func=self.thread_reputation_timer) + return True def sync_db_configuration(self) -> None: @@ -135,7 +181,7 @@ class Defender(): insert = self.Base.db_execute_query('INSERT INTO def_config (datetime, parameter, value) VALUES (:datetime, :parameter, :value)', mes_donnees) insert_rows = insert.rowcount if insert_rows > 0: - self.Irc.debug(f'Row affected into def_config : {insert_rows}') + self.Irc.Base.logs.debug(f'Row affected into def_config : {insert_rows}') # Inserer une nouvelle configuration for param, value in self.defConfig.items(): @@ -149,7 +195,7 @@ class Defender(): insert = self.Base.db_execute_query('INSERT INTO def_config (datetime, parameter, value) VALUES (:datetime, :parameter, :value)', mes_donnees) insert_rows = insert.rowcount if insert_rows > 0: - self.Irc.debug(f'DB_Def_config - new param included : {insert_rows}') + self.Irc.Base.logs.debug(f'DB_Def_config - new param included : {insert_rows}') # Supprimer un parameter si il n'existe plus dans la variable global query = "SELECT parameter FROM def_config" @@ -162,7 +208,7 @@ class Defender(): delete = self.Base.db_execute_query('DELETE FROM def_config WHERE parameter = :parameter', mes_donnees) row_affected = delete.rowcount if row_affected > 0: - self.Irc.debug(f'DB_Def_config - param [{dbparam[0]}] has been deleted') + self.Irc.Base.logs.debug(f'DB_Def_config - param [{dbparam[0]}] has been deleted') # Synchroniser la base de données avec la variable global query = "SELECT parameter, value FROM def_config" @@ -172,13 +218,13 @@ class Defender(): for param, value in result: self.defConfig[param] = self.Base.int_if_possible(value) - self.Irc.debug(self.defConfig) + self.Irc.Base.logs.debug(self.defConfig) return None def update_db_configuration(self, param:str, value:str) -> None: if not param in self.defConfig: - self.Irc.debug(f"Le parametre {param} n'existe pas dans la variable global") + self.Irc.Base.logs.error(f"Le parametre {param} n'existe pas dans la variable global") return None mes_donnees = {'parameter': param} @@ -192,10 +238,10 @@ class Defender(): updated_rows = update.rowcount if updated_rows > 0: self.defConfig[param] = self.Base.int_if_possible(value) - self.Irc.debug(f'DB_Def_config - new param updated : {param} {value}') + self.Irc.Base.logs.debug(f'DB_Def_config - new param updated : {param} {value}') + + self.Irc.Base.logs.debug(self.defConfig) - self.Irc.debug(self.defConfig) - def add_defender_channel(self, channel:str) -> bool: """Cette fonction ajoute les salons de join de Defender @@ -261,7 +307,7 @@ class Defender(): secret_code = self.Base.get_random(8) if not uid in self.Irc.db_uid: - self.Irc.debug(f'Etrange UID {uid}') + self.Irc.Base.logs.error(f'Etrange UID {uid}') return None if uid in self.db_reputation: @@ -269,7 +315,7 @@ class Defender(): self.db_reputation[uid]['updated_datetime'] = currentDateTime self.db_reputation[uid]['secret_code'] = secret_code else: - self.Irc.debug(f"L'UID {uid} n'existe pas dans REPUTATION_DB") + self.Irc.Base.logs.error(f"L'UID {uid} n'existe pas dans REPUTATION_DB") return None @@ -284,7 +330,7 @@ class Defender(): if uid in self.db_reputation: # Si le nickname existe dans le dictionnaire alors le supprimer del self.db_reputation[uid] - self.Irc.debug(f"Le UID {uid} a été supprimé du REPUTATION_DB") + self.Irc.Base.logs.debug(f"Le UID {uid} a été supprimé du REPUTATION_DB") def insert_db_trusted(self, uid: str, nickname:str) -> None: @@ -353,7 +399,7 @@ class Defender(): return uptime_minutes - def system_reputation(self, uid:str)->None: + def system_reputation(self, uid:str)-> None: # Reputation security # - Activation ou désactivation du système --> OK # - Le user sera en mesure de changer la limite de la réputation --> OK @@ -392,22 +438,23 @@ class Defender(): if chan != salon_jail: self.Irc.send2socket(f":{service_id} MODE {chan} +b {jailed_nickname}!*@*") self.Irc.send2socket(f":{service_id} KICK {chan} {jailed_nickname}") - - self.Irc.debug(f"system_reputation : {jailed_nickname} à été capturé par le système de réputation") + + self.Irc.Base.logs.info(f"system_reputation : {jailed_nickname} à été capturé par le système de réputation") # self.Irc.create_ping_timer(int(self.defConfig['reputation_timer']) * 60, 'Defender', 'system_reputation_timer') - self.Base.create_timer(int(self.defConfig['reputation_timer']) * 60, self.system_reputation_timer) + # self.Base.create_timer(int(self.defConfig['reputation_timer']) * 60, self.system_reputation_timer) else: - self.Irc.debug(f"system_reputation : {jailed_nickname} à été supprimé du système de réputation car connecté via WebIrc ou il est dans la 'Trusted list'") + self.Irc.Base.logs.info(f"system_reputation : {jailed_nickname} à été supprimé du système de réputation car connecté via WebIrc ou il est dans la 'Trusted list'") self.delete_db_reputation(uid) except IndexError as e: - self.Irc.debug(f"system_reputation : {str(e)}") + self.Irc.Base.logs.error(f"system_reputation : {str(e)}") def system_reputation_timer(self) -> None: try: reputation_flag = int(self.defConfig['reputation']) reputation_timer = int(self.defConfig['reputation_timer']) reputation_seuil = self.defConfig['reputation_seuil'] + ban_all_chan = self.Base.int_if_possible(self.defConfig['reputation_ban_all_chan']) service_id = self.Config.SERVICE_ID dchanlog = self.Config.SERVICE_CHANLOG color_red = self.Config.CONFIG_COLOR['rouge'] @@ -419,25 +466,25 @@ class Defender(): elif reputation_timer == 0: return None - # self.Irc.debug(self.db_reputation) uid_to_clean = [] for uid in self.db_reputation: if not self.db_reputation[uid]['isWebirc']: # Si il ne vient pas de WebIRC - self.Irc.debug(f"Nickname: {self.db_reputation[uid]['nickname']} | uptime: {self.get_user_uptime_in_minutes(uid)} | reputation time: {reputation_timer}") + # self.Irc.debug(f"Nickname: {self.db_reputation[uid]['nickname']} | uptime: {self.get_user_uptime_in_minutes(uid)} | reputation time: {reputation_timer}") if self.get_user_uptime_in_minutes(uid) >= reputation_timer and int(self.db_reputation[uid]['score']) <= int(reputation_seuil): self.Irc.send2socket(f":{service_id} PRIVMSG {dchanlog} :[{color_red} REPUTATION {color_black}] : Action sur {self.db_reputation[uid]['nickname']} aprés {str(reputation_timer)} minutes d'inactivité") # if not system_reputation_timer_action(cglobal['reputation_timer_action'], uid, self.db_reputation[uid]['nickname']): # return False self.Irc.send2socket(f":{service_id} KILL {self.db_reputation[uid]['nickname']} After {str(reputation_timer)} minutes of inactivity you should reconnect and type the password code ") - self.Irc.debug(f"Action sur {self.db_reputation[uid]['nickname']} aprés {str(reputation_timer)} minutes d'inactivité") + self.Irc.Base.logs.info(f"Nickname: {self.db_reputation[uid]['nickname']} KILLED after {str(reputation_timer)} minutes of inactivity") + uid_to_clean.append(uid) for uid in uid_to_clean: # Suppression des éléments dans {UID_DB} et {REPUTATION_DB} for chan in self.Irc.db_chan: - if chan != salon_jail: + if chan != salon_jail and ban_all_chan == 1: self.Irc.send2socket(f":{service_id} MODE {chan} -b {self.db_reputation[uid]['nickname']}!*@*") # Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}. @@ -445,7 +492,17 @@ class Defender(): self.delete_db_reputation(uid) except AssertionError as ae: - self.Irc.debug(f'Assertion Error -> {ae}') + self.Irc.Base.logs.error(f'Assertion Error -> {ae}') + + def thread_reputation_timer(self) -> None: + try: + while self.reputationTimer_isRunning: + self.system_reputation_timer() + time.sleep(5) + + return None + except ValueError as ve: + self.Irc.Base.logs.error(f"thread_reputation_timer Error : {ve}") def _execute_flood_action(self, action:str, channel:str) -> None: """DO NOT EXECUTE THIS FUNCTION WITHOUT THREADING @@ -505,10 +562,10 @@ class Defender(): get_diff_secondes = unixtime - self.flood_system[get_detected_uid]['first_msg_time'] elif self.flood_system[get_detected_uid]['nbr_msg'] > flood_message: - self.Irc.debug('system de flood detecté') + self.Irc.Base.logs.info('system de flood detecté') self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} : {color_red} {color_bold} Flood detected. Apply the +m mode (Ô_o)') self.Irc.send2socket(f":{service_id} MODE {channel} +m") - self.Irc.debug(f'FLOOD Détecté sur {get_detected_nickname} mode +m appliqué sur le salon {channel}') + self.Irc.Base.logs.info(f'FLOOD Détecté sur {get_detected_nickname} mode +m appliqué sur le salon {channel}') self.flood_system[get_detected_uid]['nbr_msg'] = 0 self.flood_system[get_detected_uid]['first_msg_time'] = unixtime @@ -533,10 +590,17 @@ class Defender(): return None def scan_ports(self, remote_ip: str) -> None: + """local_scan + + Args: + remote_ip (str): _description_ + """ + if remote_ip in self.Config.WHITELISTED_IP: + return None for port in self.Config.PORTS_TO_SCAN: newSocket = '' - newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) newSocket.settimeout(0.5) try: connection = (remote_ip, self.Base.int_if_possible(port)) @@ -548,25 +612,76 @@ class Defender(): newSocket.shutdown(socket.SHUT_RDWR) newSocket.close() except (socket.timeout, ConnectionRefusedError): - self.Irc.debug(f"Le port {str(port)} est fermé") + self.Base.logs.info(f"Le port {remote_ip}:{str(port)} est fermé") except AttributeError as ae: - self.Irc.debug(f"AttributeError : {ae}") + self.Base.logs.warning(f"AttributeError ({remote_ip}): {ae}") + except socket.gaierror as err: + self.Base.logs.warning(f"Address Info Error ({remote_ip}): {err}") finally: # newSocket.shutdown(socket.SHUT_RDWR) newSocket.close() - self.Irc.debug('=======> Fermeture de la socket') + self.Base.logs.info('=======> Fermeture de la socket') pass + def thread_local_scan(self) -> None: + try: + while self.localscan_isRunning: + + list_to_remove:list = [] + for ip in self.localscan_remote_ip: + self.scan_ports(ip) + list_to_remove.append(ip) + time.sleep(1) + + for ip_to_remove in list_to_remove: + self.localscan_remote_ip.remove(ip_to_remove) + + time.sleep(1) + + return None + except ValueError as ve: + self.Base.logs.warning(f"thread_local_scan Error : {ve}") + def get_ports_connexion(self, remote_ip: str) -> list[int]: + """psutil_scan + + Args: + remote_ip (str): _description_ + + Returns: + list[int]: _description_ + """ + if remote_ip in self.Config.WHITELISTED_IP: + return None + connections = psutil.net_connections(kind='inet') matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip] - self.Irc.debug(f"Connexion of {remote_ip} using ports : {str(matching_ports)}") + self.Base.logs.info(f"Connexion of {remote_ip} using ports : {str(matching_ports)}") return matching_ports - def abuseipdb_scan(self, remote_ip:str) -> dict[str, any] | None: + def thread_psutil_scan(self) -> None: + try: + while self.psutil_isRunning: + + list_to_remove:list = [] + for ip in self.psutil_remote_ip: + self.get_ports_connexion(ip) + list_to_remove.append(ip) + time.sleep(1) + + for ip_to_remove in list_to_remove: + self.psutil_remote_ip.remove(ip_to_remove) + + time.sleep(1) + + return None + except ValueError as ve: + self.Base.logs.warning(f"thread_psutil_scan Error : {ve}") + + def abuseipdb_scan(self, remote_ip:str) -> Union[dict[str, any], None]: """Analyse l'ip avec AbuseIpDB Cette methode devra etre lancer toujours via un thread ou un timer. Args: @@ -576,6 +691,8 @@ class Defender(): dict[str, any] | None: les informations du provider keys : 'score', 'country', 'isTor', 'totalReports' """ + if remote_ip in self.Config.WHITELISTED_IP: + return None if self.defConfig['abuseipdb_scan'] == 0: return None @@ -593,11 +710,14 @@ class Defender(): 'Key': self.abuseipdb_key } - response = requests.request(method='GET', url=url, headers=headers, params=querystring) + response = requests.request(method='GET', url=url, headers=headers, params=querystring, timeout=self.timeout) # Formatted output decodedResponse = json.loads(response.text) try: + if not 'data' in decodedResponse: + return None + result = { 'score': decodedResponse['data']['abuseConfidenceScore'], 'country': decodedResponse['data']['countryCode'], @@ -610,13 +730,191 @@ class Defender(): color_red = self.Config.CONFIG_COLOR['rouge'] color_black = self.Config.CONFIG_COLOR['noire'] - self.Irc.send2socket(f":{service_id} PRIVMSG {service_chanlog} :[ {color_red}ABUSEIPDB_SCAN{color_black} ] : Connexion de {remote_ip} Score: {str(result['score'])} | Country : {result['country']} | Tor : {str(result['isTor'])} | Total Reports : {str(result['totalReports'])}") + self.Irc.send2socket(f":{service_id} PRIVMSG {service_chanlog} :[ {color_red}ABUSEIPDB_SCAN{color_black} ] : Connexion de {remote_ip} ==> Score: {str(result['score'])} | Country : {result['country']} | Tor : {str(result['isTor'])} | Total Reports : {str(result['totalReports'])}") + + if result['isTor']: + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} This server do not allow Tor connexions {str(result['isTor'])} - Detected by Abuseipdb") + elif result['score'] >= 95: + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} You were banned from this server because your abuse score is = {str(result['score'])} - Detected by Abuseipdb") response.close() return result except KeyError as ke: - self.Irc.debug(f"AbuseIpDb KeyError : {ke}") + self.Base.logs.error(f"AbuseIpDb KeyError : {ke}") + except requests.ReadTimeout as rt: + self.Base.logs.error(f"AbuseIpDb Timeout : {rt}") + except requests.ConnectionError as ce: + self.Base.logs.error(f"AbuseIpDb Connection Error : {ce}") + + def thread_abuseipdb_scan(self) -> None: + try: + while self.abuseipdb_isRunning: + + list_to_remove:list = [] + for ip in self.abuseipdb_remote_ip: + self.abuseipdb_scan(ip) + list_to_remove.append(ip) + time.sleep(1) + + for ip_to_remove in list_to_remove: + self.abuseipdb_remote_ip.remove(ip_to_remove) + + time.sleep(1) + + return None + except ValueError as ve: + self.Base.logs.error(f"thread_abuseipdb_scan Error : {ve}") + + def freeipapi_scan(self, remote_ip:str) -> Union[dict[str, any], None]: + """Analyse l'ip avec Freeipapi + Cette methode devra etre lancer toujours via un thread ou un timer. + Args: + remote_ip (_type_): l'ip a analyser + + Returns: + dict[str, any] | None: les informations du provider + keys : 'countryCode', 'isProxy' + """ + if remote_ip in self.Config.WHITELISTED_IP: + return None + if self.defConfig['freeipapi_scan'] == 0: + return None + + service_id = self.Config.SERVICE_ID + service_chanlog = self.Config.SERVICE_CHANLOG + color_red = self.Config.CONFIG_COLOR['rouge'] + color_black = self.Config.CONFIG_COLOR['noire'] + + url = f'https://freeipapi.com/api/json/{remote_ip}' + + headers = { + 'Accept': 'application/json', + } + + response = requests.request(method='GET', url=url, headers=headers, timeout=self.timeout) + + # Formatted output + decodedResponse = json.loads(response.text) + try: + status_code = response.status_code + if status_code == 429: + self.Base.logs.warning(f'Too Many Requests - The rate limit for the API has been exceeded.') + return None + elif status_code != 200: + self.Base.logs.warning(f'status code = {str(status_code)}') + return None + + result = { + 'countryCode': decodedResponse['countryCode'] if 'countryCode' in decodedResponse else None, + 'isProxy': decodedResponse['isProxy'] if 'isProxy' in decodedResponse else None + } + + self.Irc.send2socket(f":{service_id} PRIVMSG {service_chanlog} :[ {color_red}FREEIPAPI_SCAN{color_black} ] : Connexion de {remote_ip} ==> Proxy: {str(result['isProxy'])} | Country : {str(result['countryCode'])}") + + if result['isProxy']: + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi") + response.close() + + return result + except KeyError as ke: + self.Base.logs.error(f"FREEIPAPI_SCAN KeyError : {ke}") + + def thread_freeipapi_scan(self) -> None: + try: + while self.freeipapi_isRunning: + + list_to_remove:list = [] + for ip in self.freeipapi_remote_ip: + self.freeipapi_scan(ip) + list_to_remove.append(ip) + time.sleep(1) + + for ip_to_remove in list_to_remove: + self.freeipapi_remote_ip.remove(ip_to_remove) + + time.sleep(1) + + return None + except ValueError as ve: + self.Base.logs.error(f"thread_freeipapi_scan Error : {ve}") + + def cloudfilt_scan(self, remote_ip:str) -> Union[dict[str, any], None]: + """Analyse l'ip avec cloudfilt + Cette methode devra etre lancer toujours via un thread ou un timer. + Args: + remote_ip (_type_): l'ip a analyser + + Returns: + dict[str, any] | None: les informations du provider + keys : 'countryCode', 'isProxy' + """ + if remote_ip in self.Config.WHITELISTED_IP: + return None + if self.defConfig['cloudfilt_scan'] == 0: + return None + if self.cloudfilt_key == '': + return None + + service_id = self.Config.SERVICE_ID + service_chanlog = self.Config.SERVICE_CHANLOG + color_red = self.Config.CONFIG_COLOR['rouge'] + color_black = self.Config.CONFIG_COLOR['noire'] + + url = f"https://developers18334.cloudfilt.com/" + + data = { + 'ip': remote_ip, + 'key': self.cloudfilt_key + } + + response = requests.post(url=url, data=data) + + # Formatted output + decodedResponse = json.loads(response.text) + try: + status_code = response.status_code + if status_code != 200: + self.Base.logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}') + return None + + result = { + 'countryiso': decodedResponse['countryiso'] if 'countryiso' in decodedResponse else None, + 'listed': decodedResponse['listed'] if 'listed' in decodedResponse else None, + 'listed_by': decodedResponse['listed_by'] if 'listed_by' in decodedResponse else None, + 'host': decodedResponse['host'] if 'host' in decodedResponse else None + } + + self.Irc.send2socket(f":{service_id} PRIVMSG {service_chanlog} :[ {color_red}CLOUDFILT_SCAN{color_black} ] : Connexion de {str(remote_ip)} ==> Host: {str(result['host'])} | country: {str(result['countryiso'])} | listed: {str(result['listed'])} | listed by : {str(result['listed_by'])}") + + if result['listed']: + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} You connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt") + + response.close() + + return result + except KeyError as ke: + self.Base.logs.error(f"CLOUDFILT_SCAN KeyError : {ke}") + return None + + def thread_cloudfilt_scan(self) -> None: + try: + while self.cloudfilt_isRunning: + + list_to_remove:list = [] + for ip in self.cloudfilt_remote_ip: + self.cloudfilt_scan(ip) + list_to_remove.append(ip) + time.sleep(1) + + for ip_to_remove in list_to_remove: + self.cloudfilt_remote_ip.remove(ip_to_remove) + + time.sleep(1) + + return None + except ValueError as ve: + self.Base.logs.error(f"Thread_cloudfilt_scan Error : {ve}") def cmd(self, data:list) -> None: @@ -634,18 +932,28 @@ class Defender(): self.reputation_first_connexion['ip'] = cmd[2] self.reputation_first_connexion['score'] = cmd[3] + if not self.Base.is_valid_ip(cmd[2]): + return None + # self.Base.scan_ports(cmd[2]) - if self.defConfig['local_scan'] == 1: - self.Base.create_thread(self.scan_ports, (cmd[2], )) + if self.defConfig['local_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: + self.localscan_remote_ip.append(cmd[2]) - if self.defConfig['psutil_scan'] == 1: - self.Base.create_thread(self.get_ports_connexion, (cmd[2], )) + if self.defConfig['psutil_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: + self.psutil_remote_ip.append(cmd[2]) + + if self.defConfig['abuseipdb_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: + self.abuseipdb_remote_ip.append(cmd[2]) + + if self.defConfig['freeipapi_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: + self.freeipapi_remote_ip.append(cmd[2]) + + if self.defConfig['cloudfilt_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: + self.cloudfilt_remote_ip.append(cmd[2]) - if self.defConfig['abuseipdb_scan'] == 1: - self.Base.create_thread(self.abuseipdb_scan, (cmd[2], )) # Possibilité de déclancher les bans a ce niveau. except IndexError: - self.Irc.debug(f'cmd reputation: index error') + self.Irc.Base.logs.error(f'cmd reputation: index error') match cmd[2]: @@ -698,7 +1006,7 @@ class Defender(): if uid in self.db_reputation: if reputation_flag == 1 and int(client_score) <= int(reputation_seuil): self.system_reputation(uid) - self.Irc.debug('Démarrer le systeme de reputation') + self.Base.logs.info('Démarrer le systeme de reputation') case 'SJOIN': # ['@msgid=F9B7JeHL5pj9nN57cJ5pEr;time=2023-12-28T20:47:24.305Z', ':001', 'SJOIN', '1702138958', '#welcome', ':0015L1AHL'] @@ -719,21 +1027,31 @@ class Defender(): self.Irc.send2socket(f":{service_id} MODE {parsed_chan} +b {self.db_reputation[parsed_UID]['nickname']}!*@*") self.Irc.send2socket(f":{service_id} KICK {parsed_chan} {self.db_reputation[parsed_UID]['nickname']}") - self.Irc.debug(f'SJOIN parsed_uid : {parsed_UID}') + self.Base.logs.debug(f'SJOIN parsed_uid : {parsed_UID}') except KeyError as ke: - self.Irc.debug(f"key error SJOIN : {ke}") + self.Base.logs.error(f"key error SJOIN : {ke}") case 'SLOG': # self.Base.scan_ports(cmd[7]) cmd.pop(0) - if self.defConfig['local_scan'] == 1: - self.Base.create_thread(self.scan_ports, (cmd[7], )) - if self.defConfig['psutil_scan'] == 1: - self.Base.create_thread(self.get_ports_connexion, (cmd[7], )) + if not self.Base.is_valid_ip(cmd[7]): + return None - if self.defConfig['abuseipdb_scan'] == 1: - self.Base.create_thread(self.abuseipdb_scan, (cmd[7], )) + if self.defConfig['local_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + self.localscan_remote_ip.append(cmd[7]) + + if self.defConfig['psutil_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + self.psutil_remote_ip.append(cmd[7]) + + if self.defConfig['abuseipdb_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + self.abuseipdb_remote_ip.append(cmd[7]) + + if self.defConfig['freeipapi_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + self.freeipapi_remote_ip.append(cmd[7]) + + if self.defConfig['cloudfilt_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + self.cloudfilt_remote_ip.append(cmd[7]) case 'NICK': # :0010BS24L NICK [NEWNICK] 1697917711 @@ -755,11 +1073,12 @@ class Defender(): self.Irc.send2socket(f":{service_id} MODE {chan} -b {oldnick}!*@*") self.Irc.send2socket(f":{service_id} MODE {chan} +b {newnickname}!*@*") except KeyError as ke: - self.Irc.debug(f'cmd - NICK - KeyError: {ke}') + self.Base.logs.error(f'cmd - NICK - KeyError: {ke}') case 'QUIT': # :001N1WD7L QUIT :Quit: free_znc_1 cmd.pop(0) + ban_all_chan = self.Base.int_if_possible(self.defConfig['reputation_ban_all_chan']) user_id = str(cmd[0]).replace(':','') final_UID = user_id @@ -769,7 +1088,7 @@ class Defender(): if final_UID in self.db_reputation: final_nickname = self.db_reputation[user_id]['nickname'] for chan in self.Irc.db_chan: - if chan != jail_salon: + if chan != jail_salon and ban_all_chan == 1: self.Irc.send2socket(f":{service_id} MODE {chan} -b {final_nickname}!*@*") self.delete_db_reputation(final_UID) @@ -796,9 +1115,9 @@ class Defender(): # self.Base.create_timer(timer_sent, self.Base.garbage_collector_sockets) except TypeError as te: - self.Irc.debug(f"Type Error -> {te}") + self.Base.logs.error(f"Type Error -> {te}") except ValueError as ve: - self.Irc.debug(f"Value Error -> {ve}") + self.Base.logs.error(f"Value Error -> {ve}") case 'show_reputation': @@ -821,12 +1140,11 @@ class Defender(): jailed_salon = self.Config.SALON_JAIL reputation_seuil = self.defConfig['reputation_seuil'] welcome_salon = self.Config.SALON_LIBERER - - self.Irc.debug(f"IP de {jailed_nickname} : {jailed_IP}") + + self.Base.logs.debug(f"IP de {jailed_nickname} : {jailed_IP}") link = self.Config.SERVEUR_LINK color_green = self.Config.CONFIG_COLOR['verte'] color_black = self.Config.CONFIG_COLOR['noire'] - if jailed_UID in self.db_reputation: if release_code == self.db_reputation[jailed_UID]['secret_code']: @@ -838,7 +1156,7 @@ class Defender(): self.Irc.send2socket(f":{service_id} MODE {chan} -b {jailed_nickname}!*@*") del self.db_reputation[jailed_UID] - self.Irc.debug(f'{jailed_UID} - {jailed_nickname} removed from REPUTATION_DB') + self.Base.logs.debug(f'{jailed_UID} - {jailed_nickname} removed from REPUTATION_DB') self.Irc.send2socket(f":{service_id} SAPART {jailed_nickname} {jailed_salon}") self.Irc.send2socket(f":{service_id} SAJOIN {jailed_nickname} {welcome_salon}") self.Irc.send2socket(f":{link} REPUTATION {jailed_IP} {int(reputation_seuil) + 1}") @@ -851,14 +1169,13 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {jailed_salon} : Ce n'est pas à toi de taper le mot de passe !") except IndexError: - self.Irc.debug('_hcmd code: out of index') + self.Base.logs.error('_hcmd code: out of index') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} code [code]') except KeyError as ke: - self.Irc.debug(f'_hcmd code: KeyError {ke}') + self.Base.logs.error(f'_hcmd code: KeyError {ke}') # self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} code [code]') pass - case 'reputation': # .reputation [on/off] --> activate or deactivate reputation system # .reputation set banallchan [on/off] --> activate or deactivate ban in all channel @@ -919,7 +1236,6 @@ class Defender(): self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}BAN ON ALL CHANS{self.Config.CONFIG_COLOR["noire"]} ] : Activated by {fromuser}') elif get_value == 'off': - print(get_value) if self.defConfig[key] == 0: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['rouge']}BAN ON ALL CHANS{self.Config.CONFIG_COLOR['noire']} ] : Already deactivated") return False @@ -944,15 +1260,16 @@ class Defender(): case _: pass - except IndexError: - self.Irc.debug('_hcmd reputation: out of index') + except IndexError as ie: + self.Base.logs.warning(f'{ie}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} reputation [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} reputation set banallchan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} reputation set limit [1234]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} reputation set timer [1234]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} reputation set action [kill|None]') - except ValueError: + except ValueError as ve: + self.Base.logs.warning(f'{ie}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : La valeur devrait etre un entier >= 0') case 'proxy_scan': @@ -972,6 +1289,8 @@ class Defender(): self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set local_scan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set psutil_scan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set abuseipdb_scan [ON/OFF]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set freeipapi_scan [ON/OFF]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set cloudfilt_scan [ON/OFF]') option = str(cmd[2]).lower() # => local_scan, psutil_scan, abuseipdb_scan action = str(cmd[3]).lower() # => on / off @@ -1019,14 +1338,49 @@ class Defender(): self.update_db_configuration(option, 0) self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") + case 'freeipapi_scan': + if action == 'on': + if self.defConfig[option] == 1: + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") + return None + self.update_db_configuration(option, 1) + + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") + elif action == 'off': + if self.defConfig[option] == 0: + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") + return None + + self.update_db_configuration(option, 0) + + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") + + case 'cloudfilt_scan': + if action == 'on': + if self.defConfig[option] == 1: + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") + return None + self.update_db_configuration(option, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") + elif action == 'off': + if self.defConfig[option] == 0: + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") + return None + self.update_db_configuration(option, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") + case _: self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set local_scan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set psutil_scan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set abuseipdb_scan [ON/OFF]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set freeipapi_scan [ON/OFF]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set cloudfilt_scan [ON/OFF]') else: self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set local_scan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set psutil_scan [ON/OFF]') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set abuseipdb_scan [ON/OFF]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set freeipapi_scan [ON/OFF]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} proxy_scan set cloudfilt_scan [ON/OFF]') case 'flood': # .flood on/off @@ -1083,7 +1437,7 @@ class Defender(): pass except ValueError as ve: - self.Irc.debug(f"{self.__class__.__name__} Value Error : {ve}") + self.Base.logs.error(f"{self.__class__.__name__} Value Error : {ve}") case 'status': color_green = self.Config.CONFIG_COLOR['verte'] @@ -1098,13 +1452,15 @@ class Defender(): self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {color_green if self.defConfig["local_scan"] == 1 else color_red}local_scan{color_black} ==> {self.defConfig["local_scan"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {color_green if self.defConfig["psutil_scan"] == 1 else color_red}psutil_scan{color_black} ==> {self.defConfig["psutil_scan"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {color_green if self.defConfig["abuseipdb_scan"] == 1 else color_red}abuseipdb_scan{color_black} ==> {self.defConfig["abuseipdb_scan"]}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {color_green if self.defConfig["freeipapi_scan"] == 1 else color_red}freeipapi_scan{color_black} ==> {self.defConfig["freeipapi_scan"]}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {color_green if self.defConfig["cloudfilt_scan"] == 1 else color_red}cloudfilt_scan{color_black} ==> {self.defConfig["cloudfilt_scan"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : [{color_green if self.defConfig["flood"] == 1 else color_red}Flood{color_black}] ==> {self.defConfig["flood"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : flood_action ==> Coming soon') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : flood_message ==> {self.defConfig["flood_message"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : flood_time ==> {self.defConfig["flood_time"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : flood_timer ==> {self.defConfig["flood_timer"]}') except KeyError as ke: - self.Irc.debug(f"Key Error : {ke}") + self.Base.logs.error(f"Key Error : {ke}") case 'join': @@ -1113,8 +1469,8 @@ class Defender(): self.Irc.send2socket(f':{service_id} JOIN {channel}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} JOINED {channel}') self.add_defender_channel(channel) - except IndexError: - self.Irc.debug('_hcmd join: out of index') + except IndexError as ie: + self.Base.logs.error(f'{ie}') case 'part': @@ -1127,8 +1483,8 @@ class Defender(): self.Irc.send2socket(f':{service_id} PART {channel}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} LEFT {channel}') self.delete_defender_channel(channel) - except IndexError: - self.Irc.debug('_hcmd part: out of index') + except IndexError as ie: + self.Base.logs.error(f'{ie}') case 'op' | 'o': # /mode #channel +o user @@ -1140,7 +1496,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd OP: {str(e)}') + self.Base.logs.warning(f'_hcmd OP: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') case 'deop' | 'do': @@ -1151,7 +1507,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd DEOP: {str(e)}') + self.Base.logs.warning(f'_hcmd DEOP: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') case 'owner' | 'q': @@ -1162,7 +1518,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd OWNER: {str(e)}') + self.Base.logs.warning(f'_hcmd OWNER: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') case 'deowner' | 'dq': @@ -1173,7 +1529,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd DEOWNER: {str(e)}') + self.Base.logs.warning(f'_hcmd DEOWNER: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') case 'halfop' | 'h': @@ -1184,7 +1540,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd halfop: {str(e)}') + self.Base.logs.warning(f'_hcmd halfop: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') case 'dehalfop' | 'dh': @@ -1195,7 +1551,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd DEHALFOP: {str(e)}') + self.Base.logs.warning(f'_hcmd DEHALFOP: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') case 'voice' | 'v': @@ -1206,7 +1562,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd VOICE: {str(e)}') + self.Base.logs.warning(f'_hcmd VOICE: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') case 'devoice' | 'dv': @@ -1217,7 +1573,7 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}") except IndexError as e: - self.Irc.debug(f'_hcmd DEVOICE: {str(e)}') + self.Base.logs.warning(f'_hcmd DEVOICE: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') case 'ban' | 'b': @@ -1227,9 +1583,9 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} +b {nickname}!*@*") - self.Irc.debug(f'{fromuser} has banned {nickname} from {channel}') + self.Base.logs.debug(f'{fromuser} has banned {nickname} from {channel}') except IndexError as e: - self.Irc.debug(f'_hcmd BAN: {str(e)}') + self.Base.logs.warning(f'_hcmd BAN: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') case 'unban' | 'ub': @@ -1239,9 +1595,9 @@ class Defender(): nickname = cmd[2] self.Irc.send2socket(f":{service_id} MODE {channel} -b {nickname}!*@*") - self.Irc.debug(f'{fromuser} has unbanned {nickname} from {channel}') + self.Base.logs.debug(f'{fromuser} has unbanned {nickname} from {channel}') except IndexError as e: - self.Irc.debug(f'_hcmd UNBAN: {str(e)}') + self.Base.logs.warning(f'_hcmd UNBAN: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]') case 'kick' | 'k': @@ -1257,9 +1613,9 @@ class Defender(): final_reason = ' '.join(reason) self.Irc.send2socket(f":{service_id} KICK {channel} {nickname} {final_reason}") - self.Irc.debug(f'{fromuser} has kicked {nickname} from {channel} : {final_reason}') + self.Base.logs.debug(f'{fromuser} has kicked {nickname} from {channel} : {final_reason}') except IndexError as e: - self.Irc.debug(f'_hcmd KICK: {str(e)}') + self.Base.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': @@ -1276,9 +1632,9 @@ class Defender(): self.Irc.send2socket(f":{service_id} KICK {channel} {nickname} {final_reason}") self.Irc.send2socket(f":{service_id} MODE {channel} +b {nickname}!*@*") - self.Irc.debug(f'{fromuser} has kicked and banned {nickname} from {channel} : {final_reason}') + self.Base.logs.debug(f'{fromuser} has kicked and banned {nickname} from {channel} : {final_reason}') except IndexError as e: - self.Irc.debug(f'_hcmd KICKBAN: {str(e)}') + self.Base.logs.warning(f'_hcmd KICKBAN: {str(e)}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kickban [#SALON] [NICKNAME] [REASON]') case 'info': @@ -1304,7 +1660,7 @@ class Defender(): self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : MODES : {self.Irc.db_uid[uid_query]["umodes"]}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : CONNECTION TIME : {self.Irc.db_uid[uid_query]["datetime"]}') except KeyError as ke: - self.Irc.debug(f"Key error info user : {ke}") + self.Base.logs.warning(f"Key error info user : {ke}") case 'show_users': for uid, infousers in self.Irc.db_uid.items(): diff --git a/mods/mod_test.py b/mods/mod_test.py index eb801e9..b27d27c 100644 --- a/mods/mod_test.py +++ b/mods/mod_test.py @@ -66,6 +66,10 @@ class Test(): self.core.db_execute_query(self.session, table_logs) return None + def unload(self) -> None: + + return None + def _hcmds(self, user:str, cmd: list) -> None: command = cmd[0].lower() diff --git a/version.json b/version.json new file mode 100644 index 0000000..e36f13c --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "version": "4.0.0" +} \ No newline at end of file