From 51788eab6299a76dfb2c95e0b86cca50ebc80677 Mon Sep 17 00:00:00 2001 From: 7ka1 <7ka1@noreply.localhost> Date: Thu, 5 Mar 2026 10:18:26 +0000 Subject: [PATCH] Actualiser 7lna.py --- 7lna.py | 328 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 201 insertions(+), 127 deletions(-) diff --git a/7lna.py b/7lna.py index b8db8b6..0fec627 100644 --- a/7lna.py +++ b/7lna.py @@ -5,13 +5,14 @@ import threading import subprocess import urllib.request import time -import random -import platform import datetime import glob import hashlib import string import secrets +import json +import platform +import shutil from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler @@ -21,6 +22,7 @@ from watchdog.events import FileSystemEventHandler UPDATE_URL = "https://git.7ka1.com/7ka1/7LnA_Antivirus_Linux_Free_ClamAV_Based/raw/branch/main/7lna.py" QUARANTINE_DIR = os.path.expanduser("~/.7lna_quarantine") WATCH_FOLDER = os.path.expanduser("~/Téléchargements") +HISTORY_FILE = os.path.expanduser("~/.7lna_history.json") ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") @@ -29,12 +31,11 @@ ctk.set_default_color_theme("blue") # UTILITAIRES SYSTÈME # ========================================== def send_desktop_notification(title, message, is_critical=False): - """Envoie une notification native sous Linux""" try: urgency = 'critical' if is_critical else 'normal' subprocess.Popen(['notify-send', '-u', urgency, '-a', '7LnA Security', title, message]) except Exception: - pass # Ignore si notify-send n'est pas installé + pass # ========================================== # GESTIONNAIRE DU BOUCLIER TEMPS RÉEL @@ -45,7 +46,7 @@ class RealTimeShieldHandler(FileSystemEventHandler): def on_created(self, event): if not event.is_directory: - time.sleep(1.5) # Laisse le temps au fichier d'être copié/téléchargé + time.sleep(1.5) self.app.trigger_realtime_scan(event.src_path) # ========================================== @@ -54,16 +55,23 @@ class RealTimeShieldHandler(FileSystemEventHandler): class Antivirus7LnA(ctk.CTk): def __init__(self): super().__init__() - self.title("7LnA Security Suite - Ultimate Edition V7") - self.geometry("1200x800") - self.minsize(950, 650) + self.title("7LnA Security Suite - V8 Enterprise Edition") + self.geometry("1250x850") + self.minsize(1000, 700) os.makedirs(QUARANTINE_DIR, exist_ok=True) + if not os.path.exists(HISTORY_FILE): + with open(HISTORY_FILE, "w") as f: + json.dump([], f) + self.shield_observer = None self.shield_active = False self.check_dependencies() self.setup_ui() + + # Lancement de la surveillance USB en arrière-plan + threading.Thread(target=self.monitor_usb_drives, daemon=True).start() def check_dependencies(self): try: @@ -79,7 +87,7 @@ class Antivirus7LnA(ctk.CTk): # --- BARRE LATÉRALE --- self.sidebar = ctk.CTkFrame(self, width=260, corner_radius=0, fg_color="#111827") self.sidebar.grid(row=0, column=0, sticky="nsew") - self.sidebar.grid_rowconfigure(8, weight=1) + self.sidebar.grid_rowconfigure(10, weight=1) ctk.CTkLabel(self.sidebar, text="🛡️ 7LnA Sec", font=ctk.CTkFont(size=30, weight="bold"), text_color="#3B82F6").grid(row=0, column=0, padx=20, pady=(30, 20)) @@ -89,10 +97,12 @@ class Antivirus7LnA(ctk.CTk): self.btn_audit = self.create_nav_button("⚙️ Audit Réseau", 4, "audit") self.btn_tools = self.create_nav_button("🧰 Outils Avancés", 5, "tools") self.btn_quarantine = self.create_nav_button("📦 Quarantaine", 6, "quarantine") - self.btn_update = self.create_nav_button("🔄 Mise à jour", 7, "update") + self.btn_schedule = self.create_nav_button("📅 Planification", 7, "schedule") # NOUVEAU + self.btn_history = self.create_nav_button("📜 Rapports", 8, "history") # NOUVEAU + self.btn_update = self.create_nav_button("🔄 Mise à jour", 9, "update") - self.version_label = ctk.CTkLabel(self.sidebar, text="v7.0 - Ultimate", text_color="#6B7280", font=ctk.CTkFont(weight="bold")) - self.version_label.grid(row=8, column=0, pady=20, sticky="s") + self.version_label = ctk.CTkLabel(self.sidebar, text="v8.0 - Enterprise", text_color="#6B7280", font=ctk.CTkFont(weight="bold")) + self.version_label.grid(row=10, column=0, pady=20, sticky="s") # --- CONTENEUR DES VUES --- self.views = {} @@ -102,6 +112,8 @@ class Antivirus7LnA(ctk.CTk): self.init_audit_view() self.init_tools_view() self.init_quarantine_view() + self.init_schedule_view() # NOUVEAU + self.init_history_view() # NOUVEAU self.init_update_view() self.select_view("dashboard") @@ -118,8 +130,8 @@ class Antivirus7LnA(ctk.CTk): view.grid_forget() if view_name in self.views: self.views[view_name].grid(row=0, column=1, sticky="nsew", padx=30, pady=30) - if view_name == "quarantine": - self.refresh_quarantine_list() + if view_name == "quarantine": self.refresh_quarantine_list() + if view_name == "history": self.refresh_history_list() # --- UTILITAIRES DE CONSOLE --- def setup_console_tags(self, console): @@ -131,16 +143,48 @@ class Antivirus7LnA(ctk.CTk): def get_time_prefix(self): return datetime.datetime.now().strftime("[%H:%M:%S] ") + def log_threat_to_history(self, filepath, threat_name): + try: + with open(HISTORY_FILE, "r") as f: data = json.load(f) + data.append({ + "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "file": filepath, + "threat": threat_name + }) + with open(HISTORY_FILE, "w") as f: json.dump(data, f, indent=4) + except Exception as e: print("Erreur d'historique:", e) + + # --- NOUVEAU : AUTO-DETECT USB --- + def monitor_usb_drives(self): + media_dir = f"/media/{os.getlogin()}" + if not os.path.exists(media_dir): return + + known_mounts = set(os.listdir(media_dir)) + while True: + time.sleep(3) + try: + current_mounts = set(os.listdir(media_dir)) + new_mounts = current_mounts - known_mounts + for mount in new_mounts: + full_path = os.path.join(media_dir, mount) + self.after(0, self.prompt_usb_scan, full_path, mount) + known_mounts = current_mounts + except Exception: pass + + def prompt_usb_scan(self, path, name): + send_desktop_notification("USB Détectée", f"Disque {name} branché.") + if messagebox.askyesno("Protection USB", f"Nouveau périphérique USB détecté :\n{name}\n\nVoulez-vous lancer une analyse antivirus dessus ?"): + self.select_view("scanner") + threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start() + # --- VUE : TABLEAU DE BORD --- def init_dashboard_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["dashboard"] = frame - ctk.CTkLabel(frame, text="État du Système", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) status_card = ctk.CTkFrame(frame, fg_color="#064E3B" if self.clamav_installed else "#7F1D1D", corner_radius=15) status_card.pack(fill="x", pady=10, ipady=20) - status_text = "Moteur ClamAV Opérationnel" if self.clamav_installed else "Moteur ClamAV Introuvable" ctk.CTkLabel(status_card, text=f"{'✅' if self.clamav_installed else '❌'} {status_text}", font=ctk.CTkFont(size=24, weight="bold"), text_color="white").pack(expand=True) @@ -149,67 +193,80 @@ class Antivirus7LnA(ctk.CTk): sys_info = f"🖥️ OS : {platform.system()} {platform.release()} | 👤 Compte : {os.getlogin()}" ctk.CTkLabel(sys_frame, text=sys_info, font=ctk.CTkFont(size=16, weight="bold")).pack(padx=20, pady=10, anchor="w") - info_frame = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) - info_frame.pack(fill="x", pady=10, ipady=10) - ctk.CTkLabel(info_frame, text=f"📂 Dossier Quarantaine : {QUARANTINE_DIR}\n📁 Dossier Surveillé : {WATCH_FOLDER}", justify="left", font=ctk.CTkFont(size=14)).pack(padx=20, pady=10, anchor="w") - # --- VUE : SCANNER MANUEL --- def init_scanner_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") - frame.grid_rowconfigure(3, weight=1) + frame.grid_rowconfigure(4, weight=1) frame.grid_columnconfigure((0, 1, 2), weight=1) self.views["scanner"] = frame ctk.CTkLabel(frame, text="Analyse Profonde", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, columnspan=3, sticky="w", pady=(0, 20)) + # Ligne 1 des boutons self.btn_scan_f = ctk.CTkButton(frame, text="📄 Analyser Fichier", command=lambda: self.start_manual_scan(is_dir=False), height=45, fg_color="#2563EB", hover_color="#1D4ED8") - self.btn_scan_f.grid(row=1, column=0, padx=(0, 5), sticky="ew") + self.btn_scan_f.grid(row=1, column=0, padx=(0, 5), pady=5, sticky="ew") self.btn_scan_d = ctk.CTkButton(frame, text="📁 Analyser Dossier", command=lambda: self.start_manual_scan(is_dir=True), height=45, fg_color="#4F46E5", hover_color="#4338CA") - self.btn_scan_d.grid(row=1, column=1, padx=(5, 5), sticky="ew") + self.btn_scan_d.grid(row=1, column=1, padx=(5, 5), pady=5, sticky="ew") self.btn_db_update = ctk.CTkButton(frame, text="🔄 MaJ Signatures (freshclam)", command=self.update_virus_db, height=45, fg_color="#059669", hover_color="#047857") - self.btn_db_update.grid(row=1, column=2, padx=(5, 0), sticky="ew") + self.btn_db_update.grid(row=1, column=2, padx=(5, 0), pady=5, sticky="ew") + + # NOUVEAU : Scanner Rootkit + self.btn_rootkit = ctk.CTkButton(frame, text="🕵️ Chasse aux Rootkits (rkhunter)", command=self.run_rootkit_scan, height=45, fg_color="#7C3AED", hover_color="#6D28D9") + self.btn_rootkit.grid(row=2, column=0, columnspan=3, pady=(5, 10), sticky="ew") self.scan_progress = ctk.CTkProgressBar(frame, mode="indeterminate", height=10) - self.scan_progress.grid(row=2, column=0, columnspan=3, pady=(20, 0), sticky="ew") + self.scan_progress.grid(row=3, column=0, columnspan=3, pady=(10, 0), sticky="ew") self.scan_progress.set(0) self.scan_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) - self.scan_console.grid(row=3, column=0, columnspan=3, pady=20, sticky="nsew") + self.scan_console.grid(row=4, column=0, columnspan=3, pady=20, sticky="nsew") self.setup_console_tags(self.scan_console) - self.scan_console.insert("end", f"{self.get_time_prefix()}[*] Moteur de détection V7 prêt...\n", "info") + self.scan_console.insert("end", f"{self.get_time_prefix()}[*] Moteur de détection V8 prêt...\n", "info") + + def run_rootkit_scan(self): + threading.Thread(target=self._exec_rootkit, daemon=True).start() + + def _exec_rootkit(self): + self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de l'analyse Rootkit (Requiert l'authentification)...\n", "info") + self.scan_progress.start() + try: + # pkexec permet de demander le mot de passe proprement en interface graphique Linux + process = subprocess.Popen(['pkexec', 'rkhunter', '-c', '--sk', '--report-warnings-only'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + for line in process.stdout: + self.scan_console.insert("end", line) + self.scan_console.see("end") + process.wait() + self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Analyse Rootkit terminée.\n", "success") + except FileNotFoundError: + self.scan_console.insert("end", f"{self.get_time_prefix()}[-] rkhunter n'est pas installé. (sudo apt install rkhunter)\n", "warning") + except Exception as e: + self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") + finally: + self.scan_progress.stop() + self.scan_console.see("end") def update_virus_db(self): threading.Thread(target=self._run_freshclam, daemon=True).start() def _run_freshclam(self): - self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Mise à jour des signatures...\n", "info") self.scan_progress.start() - self.btn_db_update.configure(state="disabled") try: process = subprocess.Popen(['freshclam'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) for line in process.stdout: self.scan_console.insert("end", line) self.scan_console.see("end") process.wait() - if process.returncode == 0: - self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Base de données virale à jour.\n", "success") - send_desktop_notification("Mise à jour réussie", "Les signatures ClamAV sont à jour.") - else: - self.scan_console.insert("end", f"{self.get_time_prefix()}[-] Échec ou droits root requis (sudo freshclam).\n", "warning") + self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Signatures à jour.\n", "success") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: self.scan_progress.stop() - self.scan_progress.set(0) - self.btn_db_update.configure(state="normal") - self.scan_console.see("end") def start_manual_scan(self, is_dir): path = filedialog.askdirectory() if is_dir else filedialog.askopenfilename() - if path: - threading.Thread(target=self.run_clamav_scan, args=(path, is_dir, self.scan_console), daemon=True).start() + if path: threading.Thread(target=self.run_clamav_scan, args=(path, is_dir, self.scan_console), daemon=True).start() # --- VUE : BOUCLIER TEMPS RÉEL --- def init_realtime_view(self): @@ -228,7 +285,6 @@ class Antivirus7LnA(ctk.CTk): self.rt_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.rt_console.grid(row=2, column=0, sticky="nsew") self.setup_console_tags(self.rt_console) - self.rt_console.insert("end", f"{self.get_time_prefix()}[-] Sentinelle en attente d'activation.\n") def toggle_shield(self): if not self.shield_active: @@ -238,28 +294,21 @@ class Antivirus7LnA(ctk.CTk): self.shield_active = True self.btn_toggle_shield.configure(text="Arrêter le Bouclier", fg_color="#DC2626", hover_color="#B91C1C") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[+] BOUCLIER ARMÉ : {WATCH_FOLDER}\n", "success") - send_desktop_notification("Bouclier Activé", f"Surveillance en direct de {WATCH_FOLDER}") else: self.shield_observer.stop() self.shield_active = False self.btn_toggle_shield.configure(text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[-] Bouclier désactivé.\n", "warning") - send_desktop_notification("Bouclier Désactivé", "La surveillance en temps réel est coupée.") - self.rt_console.see("end") def trigger_realtime_scan(self, filepath): self.rt_console.insert("end", f"\n{self.get_time_prefix()}⚡ Nouveau fichier : {os.path.basename(filepath)}\n", "info") threading.Thread(target=self.run_clamav_scan, args=(filepath, False, self.rt_console), daemon=True).start() - # --- MOTEUR CLAMAV (Cœur) --- + # --- MOTEUR CLAMAV --- def run_clamav_scan(self, path, is_dir, console): - if not self.clamav_installed: - console.insert("end", f"{self.get_time_prefix()}❌ Moteur introuvable.\n", "danger") - return - + if not self.clamav_installed: return console.insert("end", f"{self.get_time_prefix()}[*] Analyse : {path}\n") - if console == self.scan_console: - self.scan_progress.start() + if console == self.scan_console: self.scan_progress.start() try: cmd = ['clamscan', '-i', '--no-summary', f'--move={QUARANTINE_DIR}'] @@ -271,26 +320,25 @@ class Antivirus7LnA(ctk.CTk): for line in process.stdout: clean_line = line.strip() if "FOUND" in clean_line: + threat_details = clean_line.split(":") + threat_name = threat_details[-1].replace("FOUND", "").strip() if len(threat_details) > 1 else "Malware Inconnu" + console.insert("end", f"{self.get_time_prefix()}☠️ MENACE DÉTECTÉE : {clean_line}\n", "danger") - console.insert("end", f"{self.get_time_prefix()}🛡️ Action : Fichier mis en quarantaine.\n", "warning") infected += 1 + self.log_threat_to_history(path, threat_name) # Enregistrement dans l'historique elif clean_line: console.insert("end", f" -> {clean_line}\n") console.see("end") - process.wait() + if infected == 0: console.insert("end", f"{self.get_time_prefix()}[+] Fichier propre.\n", "success") else: - console.insert("end", f"{self.get_time_prefix()}🚨 {infected} MENACE(S) NEUTRALISÉE(S) !\n", "danger") - # Envoi d'une notification critique au système - send_desktop_notification("🚨 VIRUS NEUTRALISÉ", f"{infected} menace(s) trouvée(s) et isolée(s) dans {os.path.basename(path)}.", is_critical=True) + send_desktop_notification("🚨 VIRUS NEUTRALISÉ", f"{infected} menace(s) trouvée(s) et isolée(s).", is_critical=True) except Exception as e: console.insert("end", f"{self.get_time_prefix()}❌ Erreur Moteur : {e}\n", "danger") finally: - if console == self.scan_console: - self.scan_progress.stop() - self.scan_progress.set(0) + if console == self.scan_console: self.scan_progress.stop() console.see("end") # --- VUE : AUDIT & PARE-FEU --- @@ -299,15 +347,11 @@ class Antivirus7LnA(ctk.CTk): frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["audit"] = frame - ctk.CTkLabel(frame, text="Audit Réseau & Pare-feu", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) - btn_frame = ctk.CTkFrame(frame, fg_color="transparent") btn_frame.grid(row=1, column=0, sticky="ew", pady=(0, 10)) - ctk.CTkButton(btn_frame, text="Lancer l'Audit Processus", command=self.run_audit_thread, fg_color="#D97706", hover_color="#B45309", height=40).pack(side="left", padx=(0, 10)) ctk.CTkButton(btn_frame, text="Vérifier Statut UFW", command=self.check_firewall, fg_color="#4B5563", hover_color="#374151", height=40).pack(side="left") - self.audit_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.audit_console.grid(row=2, column=0, sticky="nsew") self.setup_console_tags(self.audit_console) @@ -318,10 +362,8 @@ class Antivirus7LnA(ctk.CTk): def perform_audit(self): self.audit_console.insert("end", f"{self.get_time_prefix()}[*] Recherche de connexions fantômes (Ports ouverts)...\n", "info") - try: - self.audit_console.insert("end", subprocess.check_output(['ss', '-tuln'], text=True)) + try: self.audit_console.insert("end", subprocess.check_output(['ss', '-tuln'], text=True)) except: pass - self.audit_console.insert("end", f"\n{self.get_time_prefix()}[*] Top 10 Processus (Charge CPU)...\n", "info") try: res = subprocess.check_output(['ps', '-eo', 'pid,user,%cpu,%mem,cmd', '--sort=-%cpu'], text=True) @@ -330,91 +372,134 @@ class Antivirus7LnA(ctk.CTk): def check_firewall(self): self.audit_console.delete("0.0", "end") - self.audit_console.insert("end", f"{self.get_time_prefix()}[*] Interrogation du pare-feu Ubuntu (UFW)...\n", "info") try: res = subprocess.check_output(['systemctl', 'is-active', 'ufw'], text=True).strip() - if res == "active": - self.audit_console.insert("end", f"{self.get_time_prefix()}✅ PARE-FEU UFW ACTIF.\n", "success") - else: - self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\nTerminal: 'sudo ufw enable'.\n", "danger") - except Exception: - self.audit_console.insert("end", f"{self.get_time_prefix()}[-] Impossible de déterminer l'état de UFW.\n", "warning") + if res == "active": self.audit_console.insert("end", f"{self.get_time_prefix()}✅ PARE-FEU UFW ACTIF.\n", "success") + else: self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\n", "danger") + except: self.audit_console.insert("end", f"{self.get_time_prefix()}[-] Impossible de déterminer l'état de UFW.\n", "warning") - # --- VUE : OUTILS AVANCÉS (V7 - Scrollable) --- + # --- VUE : OUTILS AVANCÉS (Avec Nettoyeur) --- def init_tools_view(self): - # On utilise un CTkScrollableFrame pour empiler les outils proprement frame = ctk.CTkScrollableFrame(self, fg_color="transparent") self.views["tools"] = frame - ctk.CTkLabel(frame, text="Boîte à Outils", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) - # --- Outil 1 : Destructeur de fichiers --- + # NOUVEAU : Nettoyeur Système + clean_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) + clean_card.pack(fill="x", pady=10, ipady=15) + ctk.CTkLabel(clean_card, text="🧹 Nettoyeur Système", font=ctk.CTkFont(size=18, weight="bold"), text_color="#10B981").pack(anchor="w", padx=20, pady=(10,0)) + ctk.CTkLabel(clean_card, text="Vide la corbeille, les fichiers temporaires et le cache de l'utilisateur pour libérer de l'espace.", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) + ctk.CTkButton(clean_card, text="Nettoyer l'ordinateur", fg_color="#059669", hover_color="#047857", command=self.run_cleaner).pack(anchor="w", padx=20) + + # Destructeur de Fichiers shred_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) shred_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(shred_card, text="🔥 Destructeur de Fichiers (File Shredder)", font=ctk.CTkFont(size=18, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkLabel(shred_card, text="Écrase le fichier avec des données aléatoires (3 passes) pour empêcher sa récupération.", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) ctk.CTkButton(shred_card, text="Détruire un fichier à jamais", fg_color="#DC2626", hover_color="#991B1B", command=self.run_shredder).pack(anchor="w", padx=20) - # --- Outil 2 : Calculateur de Hash --- + # Calculateur de Hash hash_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) hash_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(hash_card, text="🧬 Extracteur d'Empreintes (Hash)", font=ctk.CTkFont(size=18, weight="bold"), text_color="#3B82F6").pack(anchor="w", padx=20, pady=(10,0)) - ctk.CTkLabel(hash_card, text="Calcule les empreintes MD5 et SHA-256 d'un fichier pour vérifier sa légitimité.", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) - self.hash_result_var = ctk.StringVar(value="Aucun fichier sélectionné.") ctk.CTkButton(hash_card, text="Sélectionner un fichier", fg_color="#2563EB", hover_color="#1D4ED8", command=self.calculate_hash).pack(anchor="w", padx=20, pady=(0, 10)) ctk.CTkEntry(hash_card, textvariable=self.hash_result_var, state="readonly", width=500, fg_color="#111827").pack(anchor="w", padx=20) - # --- Outil 3 : Générateur de Mot de Passe --- - pwd_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) - pwd_card.pack(fill="x", pady=10, ipady=15) - ctk.CTkLabel(pwd_card, text="🔑 Générateur de Mot de Passe Blindé", font=ctk.CTkFont(size=18, weight="bold"), text_color="#10B981").pack(anchor="w", padx=20, pady=(10,0)) - ctk.CTkLabel(pwd_card, text="Génère un mot de passe cryptographiquement sécurisé (20 caractères).", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) + def run_cleaner(self): + freed_space = 0 + paths_to_clean = [os.path.expanduser("~/.local/share/Trash/files"), os.path.expanduser("~/.cache/thumbnails")] - self.pwd_result_var = ctk.StringVar(value="*****") - btn_pwd_frame = ctk.CTkFrame(pwd_card, fg_color="transparent") - btn_pwd_frame.pack(anchor="w", padx=20, fill="x") + for path in paths_to_clean: + if os.path.exists(path): + for item in os.listdir(path): + item_path = os.path.join(path, item) + try: + freed_space += os.path.getsize(item_path) + if os.path.isfile(item_path): os.remove(item_path) + elif os.path.isdir(item_path): shutil.rmtree(item_path) + except: pass - ctk.CTkButton(btn_pwd_frame, text="Générer", fg_color="#059669", hover_color="#047857", command=self.generate_password).pack(side="left", padx=(0, 10)) - ctk.CTkEntry(btn_pwd_frame, textvariable=self.pwd_result_var, state="normal", width=300, fg_color="#111827", font=ctk.CTkFont(family="Consolas", size=14)).pack(side="left") + freed_mb = freed_space / (1024 * 1024) + messagebox.showinfo("Nettoyage Terminé", f"Le nettoyage est terminé.\nEspace libéré : {freed_mb:.2f} MB") def run_shredder(self): filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ LE FICHIER À DÉTRUIRE") if not filepath: return - if messagebox.askyesno("DANGER", f"Détruire DÉFINITIVEMENT ?\n\n{filepath}"): - threading.Thread(target=self.shred_file, args=(filepath,), daemon=True).start() - - def shred_file(self, filepath): - try: - file_size = os.path.getsize(filepath) - for _ in range(3): - with open(filepath, "ba+", buffering=0) as f: - f.seek(0) - f.write(os.urandom(file_size)) - os.remove(filepath) - messagebox.showinfo("Succès", "Fichier détruit de manière irréversible.") - except Exception as e: - messagebox.showerror("Erreur", f"Erreur : {e}") + try: + file_size = os.path.getsize(filepath) + for _ in range(3): + with open(filepath, "ba+", buffering=0) as f: + f.seek(0) + f.write(os.urandom(file_size)) + os.remove(filepath) + messagebox.showinfo("Succès", "Fichier détruit.") + except Exception as e: messagebox.showerror("Erreur", str(e)) def calculate_hash(self): - filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ UN FICHIER") + filepath = filedialog.askopenfilename() if not filepath: return - try: sha256_hash = hashlib.sha256() with open(filepath, "rb") as f: - for byte_block in iter(lambda: f.read(4096), b""): - sha256_hash.update(byte_block) - + for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) self.hash_result_var.set(f"SHA-256: {sha256_hash.hexdigest()}") - except Exception as e: - self.hash_result_var.set(f"Erreur de lecture.") + except: self.hash_result_var.set("Erreur de lecture.") - def generate_password(self): - alphabet = string.ascii_letters + string.digits + "!@#$%^&*" - pwd = ''.join(secrets.choice(alphabet) for i in range(20)) - self.pwd_result_var.set(pwd) + # --- NOUVEAU : VUE PLANIFICATION (CRON) --- + def init_schedule_view(self): + frame = ctk.CTkFrame(self, fg_color="transparent") + self.views["schedule"] = frame + ctk.CTkLabel(frame, text="Planification de Scans", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) + ctk.CTkLabel(frame, text="Automatisez l'analyse de votre système via les tâches planifiées Linux (Cron).", text_color="#9CA3AF").pack(anchor="w", pady=(0, 20)) + + card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) + card.pack(fill="x", pady=10, ipady=15) + + ctk.CTkLabel(card, text="Programmer un scan automatique de ~/Téléchargements", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=10) + ctk.CTkButton(card, text="Activer le Scan Quotidien (12h00)", fg_color="#2563EB", hover_color="#1D4ED8", command=self.setup_cronjob).pack(pady=10) + ctk.CTkButton(card, text="Désactiver les scans programmés", fg_color="#4B5563", hover_color="#374151", command=self.remove_cronjob).pack(pady=10) + + def setup_cronjob(self): + cron_command = f"0 12 * * * /usr/bin/clamscan -r --move={QUARANTINE_DIR} {WATCH_FOLDER} > /dev/null 2>&1" + try: + # Récupère l'ancien crontab, enlève nos anciennes tâches, ajoute la nouvelle + os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}"; echo "{cron_command}") | crontab -') + messagebox.showinfo("Succès", "Scan automatique configuré tous les jours à 12h00.") + except Exception as e: + messagebox.showerror("Erreur", "Impossible de configurer Cron.") + + def remove_cronjob(self): + try: + os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}") | crontab -') + messagebox.showinfo("Succès", "Planification désactivée.") + except: pass + + # --- NOUVEAU : VUE HISTORIQUE --- + def init_history_view(self): + frame = ctk.CTkFrame(self, fg_color="transparent") + frame.grid_rowconfigure(1, weight=1) + frame.grid_columnconfigure(0, weight=1) + self.views["history"] = frame + + header = ctk.CTkFrame(frame, fg_color="transparent") + header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) + ctk.CTkLabel(header, text="📜 Registre des Menaces", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") + + self.history_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) + self.history_console.grid(row=1, column=0, sticky="nsew") + + def refresh_history_list(self): + self.history_console.delete("0.0", "end") + try: + with open(HISTORY_FILE, "r") as f: data = json.load(f) + if not data: + self.history_console.insert("end", "\n ✅ Historique vierge. Aucun virus n'a été détecté sur cette machine.\n") + else: + for entry in reversed(data): # Du plus récent au plus ancien + self.history_console.insert("end", f"[{entry['date']}] ☠️ {entry['threat']}\n -> Fichier : {entry['file']}\n\n") + except: self.history_console.insert("end", "Impossible de lire l'historique.") # --- VUE : QUARANTAINE --- def init_quarantine_view(self): @@ -422,21 +507,17 @@ class Antivirus7LnA(ctk.CTk): frame.grid_rowconfigure(1, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["quarantine"] = frame - header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) - ctk.CTkLabel(header, text="📦 Quarantaine", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") ctk.CTkButton(header, text="🗑️ Vider", fg_color="#DC2626", hover_color="#991B1B", command=self.empty_quarantine, height=40).pack(side="right") - self.quarantine_listbox = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color="#111827", corner_radius=10) self.quarantine_listbox.grid(row=1, column=0, sticky="nsew") def refresh_quarantine_list(self): self.quarantine_listbox.delete("0.0", "end") files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) - if not files: - self.quarantine_listbox.insert("end", "\n ✅ Aucun fichier malveillant isolé.\n") + if not files: self.quarantine_listbox.insert("end", "\n ✅ Aucun fichier malveillant isolé.\n") else: self.quarantine_listbox.insert("end", f" ⚠️ {len(files)} menace(s) :\n\n") for f in files: @@ -446,7 +527,6 @@ class Antivirus7LnA(ctk.CTk): def empty_quarantine(self): files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) if not files: return - if messagebox.askyesno("Purger", "Supprimer définitivement les virus isolés ?"): for f in files: try: os.remove(f) @@ -457,25 +537,19 @@ class Antivirus7LnA(ctk.CTk): def init_update_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["update"] = frame - ctk.CTkLabel(frame, text="Mise à jour Cloud", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) - ctk.CTkLabel(frame, text="Synchronisation avec le dépôt 7ka1 Git.", text_color="#9CA3AF", font=ctk.CTkFont(size=14)).pack(anchor="w", pady=(0, 20)) - self.btn_update_soft = ctk.CTkButton(frame, text="⬇️ Mettre à jour", command=self.run_software_update, fg_color="#8B5CF6", hover_color="#7C3AED", height=45) self.btn_update_soft.pack(anchor="w") - self.update_console = ctk.CTkTextbox(frame, height=200, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.update_console.pack(fill="x", pady=20) self.setup_console_tags(self.update_console) def run_software_update(self): self.btn_update_soft.configure(state="disabled") - self.update_console.insert("end", f"{self.get_time_prefix()}[*] Négociation avec git.7ka1.com...\n", "info") - + self.update_console.insert("end", f"{self.get_time_prefix()}[*] Négociation avec serveur...\n", "info") try: current_file_path = os.path.abspath(__file__) urllib.request.urlretrieve(UPDATE_URL, current_file_path) - self.update_console.insert("end", f"{self.get_time_prefix()}[+] Code source mis à jour !\n", "success") messagebox.showinfo("Succès", "Redémarrage requis.") self.destroy()