From 6ba0551feef1920e2772d5bf6cf4e367a3a2f679 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sun, 1 Sep 2024 12:51:54 +0200 Subject: [PATCH] v5.1.5 --- core/base.py | 17 ++++-- core/installation.py | 15 ++++- core/irc.py | 44 +++++++++------ core/loadConf.py | 128 +++++++++++++++++++++++++++++++++---------- mods/mod_command.py | 22 +++++++- mods/mod_votekick.py | 11 ++-- version.json | 2 +- 7 files changed, 179 insertions(+), 60 deletions(-) diff --git a/core/base.py b/core/base.py index 18a28c7..2c55877 100644 --- a/core/base.py +++ b/core/base.py @@ -9,9 +9,9 @@ from core.loadConf import ConfigDataModel class Base: - CORE_DB_PATH = 'core' + os.sep + 'db' + os.sep # Le dossier bases de données core - MODS_DB_PATH = 'mods' + os.sep + 'db' + os.sep # Le dossier bases de données des modules - PYTHON_MIN_VERSION = '3.10' # Version min de python + # CORE_DB_PATH = 'core' + os.sep + 'db' + os.sep # Le dossier bases de données core + # MODS_DB_PATH = 'mods' + os.sep + 'db' + os.sep # Le dossier bases de données des modules + # PYTHON_MIN_VERSION = '3.10' # Version min de python def __init__(self, Config: ConfigDataModel) -> None: @@ -26,6 +26,7 @@ class Base: self.lock = threading.RLock() # Création du lock + self.install: bool = False # Initialisation de la variable d'installation self.engine, self.cursor = self.db_init() # Initialisation de la connexion a la base de données self.__create_db() # Initialisation de la base de données @@ -200,7 +201,7 @@ class Base: else: return False - def db_record_module(self, user_cmd:str, module_name:str) -> None: + def db_record_module(self, user_cmd:str, module_name:str, isdefault:int = 0) -> None: """Enregistre les modules dans la base de données Args: @@ -210,7 +211,7 @@ class Base: if not self.db_isModuleExist(module_name): self.logs.debug(f"Le module {module_name} n'existe pas alors ont le créer") insert_cmd_query = f"INSERT INTO {self.Config.table_module} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)" - mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': 0} + mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': isdefault} self.db_execute_query(insert_cmd_query, mes_donnees) else: self.logs.debug(f"Le module {module_name} existe déja dans la base de données") @@ -532,6 +533,7 @@ class Base: full_path_db = self.Config.db_path + self.Config.db_name if not os.path.exists(db_directory): + self.install = True os.makedirs(db_directory) engine = create_engine(f'sqlite:///{full_path_db}.db', echo=False) @@ -600,6 +602,11 @@ class Base: self.db_execute_query(table_core_channel) self.db_execute_query(table_core_config) + if self.install: + self.db_record_module('sys', 'mod_command', 1) + self.db_record_module('sys', 'mod_defender', 1) + self.install = False + return None def db_execute_query(self, query:str, params:dict = {}) -> CursorResult: diff --git a/core/installation.py b/core/installation.py index 2780b52..daaabea 100644 --- a/core/installation.py +++ b/core/installation.py @@ -70,12 +70,23 @@ class Install: venv_pip_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}pip', venv_python_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}python' ) - # Exclude Windows OS if os.name == 'nt': #print('/!\\ Skip installation /!\\') self.skip_install = True + else: + if self.is_root(): + self.skip_install = True + + def is_root(self) -> bool: + + if os.geteuid() != 0: + return False + elif os.geteuid() == 0: + print('/!\\ Do not use root to install Defender /!\\') + self.Logs.critical('/!\\ Do not use root to install Defender /!\\') + return True def do_install(self) -> bool: @@ -192,7 +203,7 @@ SyslogIdentifier=Defender Restart=on-failure [Install] -WantedBy=multi-user.target +WantedBy=default.target ''' # Check if user systemd is available (.config/systemd/user/) if not os.path.exists(self.config.unix_systemd_folder): diff --git a/core/irc.py b/core/irc.py index 794e3ca..99853d9 100644 --- a/core/irc.py +++ b/core/irc.py @@ -1,7 +1,7 @@ import ssl, re, importlib, sys, time, threading, socket from ssl import SSLSocket from datetime import datetime, timedelta -from typing import Union +from typing import Union, Literal from core.loadConf import Config from core.Model import User, Admin, Channel, Clones from core.base import Base @@ -21,6 +21,8 @@ class Irc: 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 + """0: utf-8 | 1: iso-8859-1""" + self.SSL_VERSION = None # Version SSL self.Config = Config().ConfigObject @@ -200,19 +202,22 @@ class Irc: version = self.Config.current_version unixtime = self.Base.get_unixtime() + charset = self.CHARSET[0] # Envoyer un message d'identification - writer.send(f":{sid} PASS :{password}\r\n".encode('utf-8')) - writer.send(f":{sid} PROTOCTL SID NOQUIT NICKv2 SJOIN SJ3 NICKIP TKLEXT2 NEXTBANS CLK EXTSWHOIS MLOCK MTAGS\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":{sid} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8')) + writer.send(f":{sid} PASS :{password}\r\n".encode(charset)) + writer.send(f":{sid} PROTOCTL SID NOQUIT NICKv2 SJOIN SJ3 NICKIP TKLEXT2 NEXTBANS CLK EXTSWHOIS MLOCK MTAGS\r\n".encode(charset)) + # writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode(charset)) + writer.send(f":{sid} PROTOCTL EAUTH={link},,,{service_name}-v{version}\r\n".encode(charset)) + writer.send(f":{sid} PROTOCTL SID={sid}\r\n".encode(charset)) + writer.send(f":{sid} SERVER {link} 1 :{info}\r\n".encode(charset)) + writer.send(f":{sid} {nickname} :Reserved for services\r\n".encode(charset)) + writer.send(f":{sid} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * * :{realname}\r\n".encode(charset)) + writer.send(f":{sid} SJOIN {unixtime} {chan} + :{service_id}\r\n".encode(charset)) + writer.send(f":{sid} TKL + Q * {nickname} {host} 0 {unixtime} :Reserved for services\r\n".encode(charset)) + + writer.send(f":{service_id} MODE {chan} +{cmodes}\r\n".encode(charset)) + writer.send(f":{service_id} MODE {chan} +{umodes} {service_id}\r\n".encode(charset)) self.Base.logs.debug('Link information sent to the server') @@ -221,9 +226,9 @@ class Irc: self.Base.logs.critical(f'{ae}') def __join_saved_channels(self) -> None: - - core_table = 'core_channel' - + """## Joining saved channels""" + core_table = self.Config.table_channel + query = f'''SELECT distinct channel_name FROM {core_table}''' exec_query = self.Base.db_execute_query(query) result_query = exec_query.fetchall() @@ -686,7 +691,10 @@ class Irc: else: version = f'{current_version}' - self.send2socket(f"JOIN {self.Config.SERVICE_CHANLOG}") + # self.send2socket(f":{self.Config.SERVICE_NICKNAME} SVSJOIN {self.Config.SERVICE_NICKNAME} {self.Config.SERVICE_CHANLOG}") + # self.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.SERVICE_CHANLOG} +o {self.Config.SERVICE_NICKNAME}") + # self.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.SERVICE_CHANLOG} +{self.Config.SERVICE_CMODES}") + print(f"################### DEFENDER ###################") print(f"# SERVICE CONNECTE ") print(f"# SERVEUR : {self.Config.SERVEUR_IP} ") @@ -775,8 +783,8 @@ class Irc: for i in range(start_boucle, len(cmd)): parsed_UID = str(cmd[i]) # pattern = fr'[:|@|%|\+|~|\*]*' - pattern = fr':' - parsed_UID = re.sub(pattern, '', parsed_UID) + # pattern = fr':' + # parsed_UID = re.sub(pattern, '', parsed_UID) clean_uid = self.Base.clean_uid(parsed_UID) if len(clean_uid) == 9: list_users.append(parsed_UID) diff --git a/core/loadConf.py b/core/loadConf.py index 79557b7..7002207 100644 --- a/core/loadConf.py +++ b/core/loadConf.py @@ -1,6 +1,6 @@ import json, sys from os import sep -from typing import Union +from typing import Union, Literal from dataclasses import dataclass, field ########################################## @@ -11,58 +11,128 @@ from dataclasses import dataclass, field class ConfigDataModel: SERVEUR_IP: str - SERVEUR_HOSTNAME: str # Le hostname du serveur IRC - SERVEUR_LINK: str # Host attendu par votre IRCd (ex. dans votre link block pour Unrealircd) - SERVEUR_PORT: int # Port du link - SERVEUR_PASSWORD: str # Mot de passe du link (Privilégiez argon2 sur Unrealircd) - SERVEUR_ID: str # SID (identification) du bot en tant que Services - SERVEUR_SSL: bool # Activer la connexion SSL + """Server public IP (could be 127.0.0.1 localhost)""" - SERVICE_NAME: str # Le nom du service - SERVICE_NICKNAME: str # Nick du bot sur IRC - SERVICE_REALNAME: str # Realname du bot - SERVICE_USERNAME: str # Ident du bot - SERVICE_HOST: str # Host du bot - SERVICE_INFO: str # swhois du bot - SERVICE_CHANLOG: str # Salon des logs et autres messages issus du bot - SERVICE_SMODES: str # Mode du service - SERVICE_CMODES: str # Mode du salon (#ChanLog) que le bot appliquera à son entrée - SERVICE_UMODES: str # Mode que le bot pourra se donner à sa connexion au salon chanlog - SERVICE_PREFIX: str # Prefix pour envoyer les commandes au bot - SERVICE_ID: str = field(init=False) # L'identifiant du service + SERVEUR_HOSTNAME: str + """IRC Server Hostname (your.hostname.extension)""" - OWNER: str # Identifiant du compte admin - PASSWORD: str # Mot de passe du compte admin + SERVEUR_LINK: str + """The link hostname (should be the same as your unrealircd link block)""" - SALON_JAIL: str # Salon pot de miel - SALON_JAIL_MODES: str # Mode du salon pot de miel - SALON_LIBERER: str # Le salon ou sera envoyé l'utilisateur clean + SERVEUR_PORT: int + """Server port as configured in your unrealircd link block""" - API_TIMEOUT: int # Timeout des api's + SERVEUR_PASSWORD: str + """Your link password""" - PORTS_TO_SCAN: list # Liste des ports a scanné pour une detection de proxy - WHITELISTED_IP: list # IP a ne pas scanner - GLINE_DURATION: str # La durée du gline + SERVEUR_ID: str + """Service identification could be Z01 should be unique""" - DEBUG_LEVEL: int # Le niveau des logs DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50 + SERVEUR_SSL: bool + """Activate SSL connexion""" + + SERVICE_NAME: str + """Service name (Ex. Defender)""" + + SERVICE_NICKNAME: str + """Nickname of the service (Ex. Defender)""" + + SERVICE_REALNAME: str + """Realname of the service""" + + SERVICE_USERNAME: str + """Username of the service""" + + SERVICE_HOST: str + """The service hostname""" + + SERVICE_INFO: str + """Swhois of the service""" + + SERVICE_CHANLOG: str + """The channel used by the service (ex. #services)""" + + SERVICE_SMODES: str + """The service mode (ex. +ioqBS)""" + + SERVICE_CMODES: str + """The mode of the log channel (ex. ntsO)""" + + SERVICE_UMODES: str + """The mode of the service when joining chanlog (ex. o, the service will be operator in the chanlog)""" + + SERVICE_PREFIX: str + """The default prefix to communicate with the service""" + + SERVICE_ID: str = field(init=False) + """The service unique ID""" + + OWNER: str + """The nickname of the admin of the service""" + + PASSWORD: str + """The password of the admin of the service""" + + SALON_JAIL: str + """The JAIL channel (ex. #jail)""" + + SALON_JAIL_MODES: str + """The jail channel modes (ex. sS)""" + + SALON_LIBERER: str + """Channel where the nickname will be released""" + + API_TIMEOUT: int + """Default api timeout in second""" + + PORTS_TO_SCAN: list + """List of ports to scan available for proxy_scan in the mod_defender module""" + + WHITELISTED_IP: list + """List of remote IP to don't scan""" + + GLINE_DURATION: str + """Gline duration""" + + DEBUG_LEVEL:Literal[10, 20, 30, 40, 50] # Le niveau des logs DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50 + """Logs level: DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50""" CONFIG_COLOR: dict[str, str] table_admin: str + """Admin table""" + table_commande: str + """Core command table""" + table_log: str + """Core log table""" + table_module: str + """Core module table""" + table_config: str + """Core configuration table""" + table_channel: str + """Core channel table""" current_version: str + """Current version of Defender""" + latest_version: str + """The Latest version fetched from github""" + db_name: str + """The database name""" + db_path: str + """The database path""" def __post_init__(self): # Initialiser SERVICE_ID après la création de l'objet self.SERVICE_ID:str = f"{self.SERVEUR_ID}AAAAAB" + """The service ID which is SERVEUR_ID and AAAAAB""" class Config: diff --git a/mods/mod_command.py b/mods/mod_command.py index cba38ef..858fa1e 100644 --- a/mods/mod_command.py +++ b/mods/mod_command.py @@ -35,7 +35,7 @@ class Command(): # Create module commands (Mandatory) self.commands_level = { 1: ['join', 'part'], - 2: ['owner', 'deowner', 'op', 'deop', 'halfop', 'dehalfop', 'voice', 'devoice', 'ban', 'unban','kick', 'kickban', 'umode'] + 2: ['owner', 'deowner', 'op', 'deop', 'halfop', 'dehalfop', 'voice', 'devoice', 'deopall', 'devoiceall', 'voiceall', 'ban', 'unban','kick', 'kickban', 'umode'] } # Init the module @@ -172,6 +172,26 @@ class Command(): match command: + case 'deopall': + try: + self.Irc.send2socket(f":{service_id} SVSMODE {fromchannel} -o") + + except IndexError as e: + self.Logs.warning(f'_hcmd OP: {str(e)}') + + case 'devoiceall': + try: + self.Irc.send2socket(f":{service_id} SVSMODE {fromchannel} -v") + + except IndexError as e: + self.Logs.warning(f'_hcmd OP: {str(e)}') + + case 'voiceall': + chan_info = self.Channel.get_Channel(fromchannel) + for uid in chan_info.uids: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {self.User.get_nickname(self.Base.clean_uid(uid))}") + + case 'op': # /mode #channel +o user # .op #channel user diff --git a/mods/mod_votekick.py b/mods/mod_votekick.py index d6b8bab..79cff94 100644 --- a/mods/mod_votekick.py +++ b/mods/mod_votekick.py @@ -218,15 +218,18 @@ class Votekick(): dnickname = self.Config.SERVICE_NICKNAME + if not self.is_vote_ongoing(channel): + return None + for chan in self.VOTE_CHANNEL_DB: if chan.channel_name == channel: target_user = self.User.get_nickname(chan.target_user) if chan.vote_for > chan.vote_against: - self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :The user {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} will be kicked from this channel') + self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :User {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll be kicked from the channel') self.Irc.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") self.Channel.delete_user_from_channel(channel, self.User.get_uid(target_user)) elif chan.vote_for <= chan.vote_against: - self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user [{target_user}] will stay on this channel') + self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :User {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll remain in the channel') # Init the system if self.init_vote_system(channel): @@ -323,10 +326,10 @@ class Votekick(): if chan.channel_name == channel: target_user = self.User.get_nickname(chan.target_user) if chan.vote_for > chan.vote_against: - self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :The user {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} will be kicked from this channel') + self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :User {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll be kicked from the channel') self.Irc.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") elif chan.vote_for <= chan.vote_against: - self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user will stay on this channel') + self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :User {self.Config.CONFIG_COLOR["gras"]}{target_user}{self.Config.CONFIG_COLOR["nogc"]} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll remain in the channel') # Init the system if self.init_vote_system(channel): diff --git a/version.json b/version.json index 52c9599..7bffc70 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "5.1.0" + "version": "5.1.5" } \ No newline at end of file