diff --git a/core/base.py b/core/base.py index 3fc9d49..99884b0 100644 --- a/core/base.py +++ b/core/base.py @@ -153,9 +153,15 @@ class Base: except AssertionError as ae: self.__debug(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 @@ -367,7 +373,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(): diff --git a/core/irc.py b/core/irc.py index d55c0f3..5d27922 100644 --- a/core/irc.py +++ b/core/irc.py @@ -38,7 +38,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 # @@ -57,9 +57,9 @@ class Irc: self.IrcSocket.connect(connexion_information) # 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 + # ssl_context = self.__ssl_context() + # ssl_connexion = ssl_context.wrap_socket(self.IrcSocket, server_hostname=self.Config.SERVEUR_HOSTNAME) + # self.IrcSocket = ssl_connexion return None @@ -78,50 +78,66 @@ 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.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.debug("--> En attente de la fermeture du socket ...") - 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 + # 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) - data_in_bytes = self.IrcSocket.recv(buffer_size) - count_bytes = len(data_in_bytes) + data_in_bytes = self.IrcSocket.recv(buffer_size) - 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("========================================================") + count_bytes = len(data_in_bytes) - data = data_in_bytes.splitlines() + 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("========================================================") - # print(f"{str(buffer_size)} - {str(len(data_in_bytes))}") + data = data_in_bytes.splitlines() + # 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) + if self.IrcSocket.fileno() == -1: + print('restarting the socket') + self.RESTART = 1 + + except ssl.SSLEOFError as soe: + self.debug(f"SSLEOFError __connect_to_irc: {soe} - {data}") + except ssl.SSLError as se: + self.debug(f"SSLError __connect_to_irc: {se} - {data}") + except OSError as oe: + self.debug(f"SSLError __connect_to_irc: {oe} - {data}") + except Exception as e: + self.debug(f"Exception __connect_to_irc: {e} - {data}") self.IrcSocket.shutdown(socket.SHUT_RDWR) self.IrcSocket.close() + self.debug("--> Fermeture de Defender ...") except AssertionError as ae: self.debug(f'Assertion error : {ae}') except ValueError as ve: self.debug(f'Value Error : {ve}') - except OSError as oe: - self.debug(f"OS Error : {oe}") + except ssl.SSLEOFError as soe: + self.debug(f"OS Error __connect_to_irc: {soe}") + 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 @@ -180,8 +196,12 @@ class Irc: self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0],'replace')) except AssertionError as ae: self.debug(f"Assertion error : {ae}") + except ssl.SSLEOFError as soe: + self.debug(f"SSLEOFError send2socket: {soe} - {send_message}") + except ssl.SSLError as se: + self.debug(f"SSLError send2socket: {se} - {send_message}") except OSError as oe: - self.debug(f"OS Error : {oe}") + self.debug(f"OSError send2socket: {oe} - {send_message}") def send_response(self, responses:list[bytes]) -> None: try: @@ -344,8 +364,8 @@ class Irc: except ModuleNotFoundError as moduleNotFound: self.debug(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.debug(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: @@ -603,10 +623,13 @@ class Irc: 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: return False - self.debug(cmd) + self.debug(cmd_to_debug) match cmd[0]: @@ -1028,7 +1051,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]: @@ -1051,6 +1074,7 @@ class Irc: class_name = module_name.split('_')[1].capitalize() # ==> Defender if 'mods.' + module_name in sys.modules: + self.loaded_classes[class_name].unload() self.debug('Module Already Loaded ... reload the module ...') the_module = sys.modules['mods.' + module_name] importlib.reload(the_module) @@ -1139,7 +1163,12 @@ class Irc: 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() diff --git a/mods/mod_defender.py b/mods/mod_defender.py index 7716fe2..e509f6a 100644 --- a/mods/mod_defender.py +++ b/mods/mod_defender.py @@ -1,6 +1,6 @@ from datetime import datetime from typing import Union -import re, socket, psutil, requests, json +import re, socket, psutil, requests, json, time from core.irc import Irc # Le module crée devra réspecter quelques conditions @@ -10,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(): @@ -21,6 +24,19 @@ class Defender(): self.Base = ircInstance.Base # Ajouter l'objet Base au module ( Obligatoire ) self.timeout = self.Config.API_TIMEOUT # API Timeout + 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.debug(f'Module {self.__class__.__name__} loaded ...') # Créer les nouvelles commandes du module @@ -53,6 +69,22 @@ class Defender(): return None + def unload(self) -> None: + + 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 = 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 @@ -123,6 +155,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: @@ -400,7 +440,7 @@ class Defender(): self.Irc.debug(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.delete_db_reputation(uid) @@ -453,6 +493,16 @@ class Defender(): except AssertionError as ae: self.Irc.debug(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.debug(f"thread_reputation_timer Error : {ve}") + def _execute_flood_action(self, action:str, channel:str) -> None: """DO NOT EXECUTE THIS FUNCTION WITHOUT THREADING @@ -539,7 +589,11 @@ 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 @@ -569,8 +623,34 @@ class Defender(): pass - def get_ports_connexion(self, remote_ip: str) -> list[int]: + 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.Irc.debug(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 @@ -581,6 +661,25 @@ class Defender(): return matching_ports + 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.Irc.debug(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. @@ -633,15 +732,38 @@ class Defender(): 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} 1d This server do not allow Tor connexions {str(result['isTor'])}") + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} 30 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} 1d You were banned from this server because your abuse score is = {str(result['score'])}") + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} 30 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}") + except requests.ReadTimeout as rt: + self.Irc.debug(f"AbuseIpDb Timeout : {rt}") + except requests.ConnectionError as ce: + self.Irc.debug(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.Irc.debug(f"thread_abuseipdb_scan Error : {ve}") def freeipapi_scan(self, remote_ip:str) -> Union[dict[str, any], None]: """Analyse l'ip avec Freeipapi @@ -687,16 +809,35 @@ class Defender(): '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 : {result['countryCode']}") + 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} 1d This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi") + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} 30 This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi") response.close() return result except KeyError as ke: self.Irc.debug(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.Irc.debug(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. @@ -743,10 +884,10 @@ class Defender(): '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 {remote_ip} ==> Host: {str(result['host'])} | country: {str(result['countryiso'])} | listed: {str(result['listed'])} | listed by : {result['listed_by']}") + 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} 1d You connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt") + self.Irc.send2socket(f":{service_id} GLINE +*@{remote_ip} 30 You connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt") response.close() @@ -755,6 +896,25 @@ class Defender(): self.Irc.debug(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.Irc.debug(f"Thread_cloudfilt_scan Error : {ve}") + def cmd(self, data:list) -> None: service_id = self.Config.SERVICE_ID # Defender serveur id @@ -773,19 +933,20 @@ class Defender(): # self.Base.scan_ports(cmd[2]) if self.defConfig['local_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.scan_ports, (cmd[2], )) + self.localscan_remote_ip.append(cmd[2]) if self.defConfig['psutil_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.get_ports_connexion, (cmd[2], )) + self.psutil_remote_ip.append(cmd[2]) if self.defConfig['abuseipdb_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.abuseipdb_scan, (cmd[2], )) + self.abuseipdb_remote_ip.append(cmd[2]) if self.defConfig['freeipapi_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.freeipapi_scan, (cmd[2], )) + self.freeipapi_remote_ip.append(cmd[2]) if self.defConfig['cloudfilt_scan'] == 1 and not cmd[2] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.cloudfilt_scan, (cmd[2], )) + self.cloudfilt_remote_ip.append(cmd[2]) + # Possibilité de déclancher les bans a ce niveau. except IndexError: self.Irc.debug(f'cmd reputation: index error') @@ -870,19 +1031,19 @@ class Defender(): # self.Base.scan_ports(cmd[7]) cmd.pop(0) if self.defConfig['local_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.scan_ports, (cmd[7], )) + self.localscan_remote_ip.append(cmd[7]) if self.defConfig['psutil_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.get_ports_connexion, (cmd[7], )) + self.psutil_remote_ip.append(cmd[7]) if self.defConfig['abuseipdb_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.abuseipdb_scan, (cmd[7], )) + self.abuseipdb_remote_ip.append(cmd[7]) if self.defConfig['freeipapi_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.freeipapi_scan, (cmd[7], )) + self.freeipapi_remote_ip.append[cmd[7]] if self.defConfig['cloudfilt_scan'] == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - self.Base.create_thread(self.cloudfilt_scan, (cmd[7], )) + self.cloudfilt_remote_ip.append(cmd[7]) case 'NICK': # :0010BS24L NICK [NEWNICK] 1697917711 @@ -1175,12 +1336,15 @@ class Defender(): 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':