diff --git a/7lna.py b/7lna.py index f9155d1..b8db8b6 100644 --- a/7lna.py +++ b/7lna.py @@ -6,6 +6,12 @@ import subprocess import urllib.request import time import random +import platform +import datetime +import glob +import hashlib +import string +import secrets from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler @@ -19,6 +25,17 @@ WATCH_FOLDER = os.path.expanduser("~/Téléchargements") ctk.set_appearance_mode("dark") 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é + # ========================================== # GESTIONNAIRE DU BOUCLIER TEMPS RÉEL # ========================================== @@ -28,7 +45,7 @@ class RealTimeShieldHandler(FileSystemEventHandler): def on_created(self, event): if not event.is_directory: - time.sleep(1.5) + time.sleep(1.5) # Laisse le temps au fichier d'être copié/téléchargé self.app.trigger_realtime_scan(event.src_path) # ========================================== @@ -37,9 +54,9 @@ class RealTimeShieldHandler(FileSystemEventHandler): class Antivirus7LnA(ctk.CTk): def __init__(self): super().__init__() - self.title("7LnA Security Suite - Advanced Edition") - self.geometry("1100x700") - self.minsize(900, 600) + self.title("7LnA Security Suite - Ultimate Edition V7") + self.geometry("1200x800") + self.minsize(950, 650) os.makedirs(QUARANTINE_DIR, exist_ok=True) self.shield_observer = None @@ -60,21 +77,22 @@ class Antivirus7LnA(ctk.CTk): self.grid_columnconfigure(1, weight=1) # --- BARRE LATÉRALE --- - self.sidebar = ctk.CTkFrame(self, width=240, corner_radius=0, fg_color="#111827") + 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(7, weight=1) + self.sidebar.grid_rowconfigure(8, weight=1) - ctk.CTkLabel(self.sidebar, text="🛡️ 7LnA Sec", font=ctk.CTkFont(size=28, weight="bold"), text_color="#3B82F6").grid(row=0, column=0, padx=20, pady=30) + 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)) self.btn_dash = self.create_nav_button("📊 Tableau de Bord", 1, "dashboard") self.btn_scan = self.create_nav_button("🔍 Scanner Manuel", 2, "scanner") self.btn_shield = self.create_nav_button("⚡ Bouclier Actif", 3, "shield") 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_update = self.create_nav_button("🔄 Mise à jour", 6, "update") + self.btn_quarantine = self.create_nav_button("📦 Quarantaine", 6, "quarantine") + self.btn_update = self.create_nav_button("🔄 Mise à jour", 7, "update") - self.version_label = ctk.CTkLabel(self.sidebar, text="v6.0 - Advanced", text_color="#6B7280") - self.version_label.grid(row=7, column=0, pady=20, sticky="s") + 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") # --- CONTENEUR DES VUES --- self.views = {} @@ -83,15 +101,16 @@ class Antivirus7LnA(ctk.CTk): self.init_realtime_view() self.init_audit_view() self.init_tools_view() + self.init_quarantine_view() self.init_update_view() self.select_view("dashboard") def create_nav_button(self, text, row, view_name): btn = ctk.CTkButton(self.sidebar, text=text, anchor="w", fg_color="transparent", - text_color="#D1D5DB", hover_color="#1F2937", font=ctk.CTkFont(size=14), + text_color="#D1D5DB", hover_color="#1F2937", font=ctk.CTkFont(size=15), height=40, command=lambda: self.select_view(view_name)) - btn.grid(row=row, column=0, padx=20, pady=10, sticky="ew") + btn.grid(row=row, column=0, padx=20, pady=5, sticky="ew") return btn def select_view(self, view_name): @@ -99,51 +118,93 @@ 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() - # --- UTILITAIRES DE CONSOLE (COULEURS) --- + # --- UTILITAIRES DE CONSOLE --- def setup_console_tags(self, console): - """Ajoute le support des couleurs dans les consoles textuelles""" - console.tag_config("danger", foreground="#EF4444") # Rouge - console.tag_config("success", foreground="#10B981") # Vert - console.tag_config("warning", foreground="#F59E0B") # Orange - console.tag_config("info", foreground="#3B82F6") # Bleu + console.tag_config("danger", foreground="#EF4444") + console.tag_config("success", foreground="#10B981") + console.tag_config("warning", foreground="#F59E0B") + console.tag_config("info", foreground="#3B82F6") + + def get_time_prefix(self): + return datetime.datetime.now().strftime("[%H:%M:%S] ") # --- 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=28, weight="bold")).pack(anchor="w", pady=(0, 20)) + 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=20, weight="bold"), text_color="white").pack(expand=True) + 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) + sys_frame = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) + sys_frame.pack(fill="x", pady=10, ipady=10) + 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=20, ipady=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(2, weight=1) - frame.grid_columnconfigure((0, 1), weight=1) + frame.grid_rowconfigure(3, weight=1) + frame.grid_columnconfigure((0, 1, 2), weight=1) self.views["scanner"] = frame - ctk.CTkLabel(frame, text="Analyse Profonde", font=ctk.CTkFont(size=28, weight="bold")).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 20)) + 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)) - self.btn_scan_f = ctk.CTkButton(frame, text="📄 Analyser un 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, 10), sticky="ew") + 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_d = ctk.CTkButton(frame, text="📁 Analyser un 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=(10, 0), 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.scan_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827") - self.scan_console.grid(row=2, column=0, columnspan=2, pady=20, sticky="nsew") + 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.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.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.setup_console_tags(self.scan_console) - self.scan_console.insert("end", "[*] Moteur prêt. En attente de cible...\n", "info") + self.scan_console.insert("end", f"{self.get_time_prefix()}[*] Moteur de détection V7 prêt...\n", "info") + + 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") + 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() @@ -160,14 +221,14 @@ class Antivirus7LnA(ctk.CTk): header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) - ctk.CTkLabel(header, text="⚡ Bouclier Actif", font=ctk.CTkFont(size=28, weight="bold")).pack(side="left") - self.btn_toggle_shield = ctk.CTkButton(header, text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857", command=self.toggle_shield, height=40) + ctk.CTkLabel(header, text="⚡ Bouclier Actif", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") + self.btn_toggle_shield = ctk.CTkButton(header, text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857", command=self.toggle_shield, height=45, font=ctk.CTkFont(weight="bold")) self.btn_toggle_shield.pack(side="right") - self.rt_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827") + 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", "[-] Bouclier en attente.\n") + 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: @@ -176,25 +237,30 @@ class Antivirus7LnA(ctk.CTk): self.shield_observer.start() 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[+] BOUCLIER ARMÉ sur {WATCH_FOLDER}\n", "success") + 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", "\n[-] Bouclier désactivé.\n", "warning") + 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⚡ Fichier capté : {os.path.basename(filepath)}\n", "info") + 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) --- def run_clamav_scan(self, path, is_dir, console): if not self.clamav_installed: - console.insert("end", "❌ Erreur critique : Moteur introuvable.\n", "danger") + console.insert("end", f"{self.get_time_prefix()}❌ Moteur introuvable.\n", "danger") return - console.insert("end", f"[*] Analyse en cours : {path}\n") + console.insert("end", f"{self.get_time_prefix()}[*] Analyse : {path}\n") + if console == self.scan_console: + self.scan_progress.start() + try: cmd = ['clamscan', '-i', '--no-summary', f'--move={QUARANTINE_DIR}'] if is_dir: cmd.append('-r') @@ -205,21 +271,27 @@ class Antivirus7LnA(ctk.CTk): for line in process.stdout: clean_line = line.strip() if "FOUND" in clean_line: - console.insert("end", f"☠️ MENACE : {clean_line}\n", "danger") - console.insert("end", "🛡️ Action : Isolé en quarantaine.\n", "warning") + 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 - else: - console.insert("end", clean_line + "\n") + elif clean_line: + console.insert("end", f" -> {clean_line}\n") console.see("end") process.wait() if infected == 0: - console.insert("end", "[+] Cible sécurisée (Propre).\n", "success") + console.insert("end", f"{self.get_time_prefix()}[+] Fichier propre.\n", "success") else: - console.insert("end", f"🚨 {infected} virus neutralisé(s) !\n", "danger") + 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) except Exception as e: - console.insert("end", f"❌ Panne moteur : {e}\n", "danger") - console.see("end") + 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) + console.see("end") # --- VUE : AUDIT & PARE-FEU --- def init_audit_view(self): @@ -228,15 +300,15 @@ class Antivirus7LnA(ctk.CTk): frame.grid_columnconfigure(0, weight=1) self.views["audit"] = frame - ctk.CTkLabel(frame, text="Audit Réseau & Pare-feu", font=ctk.CTkFont(size=28, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) + 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").pack(side="left", padx=(0, 10)) - ctk.CTkButton(btn_frame, text="Vérifier Statut UFW (Pare-feu)", command=self.check_firewall, fg_color="#4B5563", hover_color="#374151").pack(side="left") + 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") + 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) @@ -245,12 +317,12 @@ class Antivirus7LnA(ctk.CTk): threading.Thread(target=self.perform_audit, daemon=True).start() def perform_audit(self): - self.audit_console.insert("end", "[*] Recherche de connexions fantômes (Backdoors)...\n", "info") + 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)) except: pass - self.audit_console.insert("end", "\n[*] Top 10 Processus (Charge CPU)...\n", "info") + 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) self.audit_console.insert("end", "\n".join(res.split('\n')[:11]) + "\n") @@ -258,85 +330,158 @@ class Antivirus7LnA(ctk.CTk): def check_firewall(self): self.audit_console.delete("0.0", "end") - self.audit_console.insert("end", "[*] Interrogation du pare-feu Ubuntu (UFW)...\n", "info") + self.audit_console.insert("end", f"{self.get_time_prefix()}[*] Interrogation du pare-feu Ubuntu (UFW)...\n", "info") try: - # systemctl est utilisé pour éviter le prompt sudo de ufw res = subprocess.check_output(['systemctl', 'is-active', 'ufw'], text=True).strip() if res == "active": - self.audit_console.insert("end", "✅ PARE-FEU UFW ACTIF ET EN LIGNE.\n", "success") + self.audit_console.insert("end", f"{self.get_time_prefix()}✅ PARE-FEU UFW ACTIF.\n", "success") else: - self.audit_console.insert("end", "⚠️ ATTENTION : PARE-FEU INACTIF.\nActivez-le via terminal avec 'sudo ufw enable'.\n", "danger") + 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", "[-] Impossible de déterminer l'état de UFW.\n", "warning") + self.audit_console.insert("end", f"{self.get_time_prefix()}[-] Impossible de déterminer l'état de UFW.\n", "warning") - # --- VUE : OUTILS AVANCÉS (NOUVEAU !) --- + # --- VUE : OUTILS AVANCÉS (V7 - Scrollable) --- def init_tools_view(self): - frame = ctk.CTkFrame(self, fg_color="transparent") + # 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=28, weight="bold")).pack(anchor="w", pady=(0, 20)) + 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 + # --- Outil 1 : 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 sécurisé (File Shredder)", font=ctk.CTkFont(size=16, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20) - ctk.CTkLabel(shred_card, text="Écrase un fichier avec des données aléatoires (3 passes) avant de le supprimer.\nEmpêche toute récupération par un logiciel tiers.", justify="left", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 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 --- + 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)) + + 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") + + 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") + def run_shredder(self): filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ LE FICHIER À DÉTRUIRE") if not filepath: return - confirm = messagebox.askyesno("DANGER IRRÉVERSIBLE", f"Voulez-vous vraiment DÉTRUIRE ce fichier ?\n\n{filepath}\n\nCette action est permanente et ne va PAS dans la corbeille.") - if confirm: + 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) - # 3 passages de réécriture - for i in range(3): + for _ in range(3): with open(filepath, "ba+", buffering=0) as f: f.seek(0) f.write(os.urandom(file_size)) - # Suppression finale du système os.remove(filepath) - messagebox.showinfo("Succès", "Fichier pulvérisé avec succès. Récupération impossible.") + messagebox.showinfo("Succès", "Fichier détruit de manière irréversible.") except Exception as e: - messagebox.showerror("Erreur Shredder", f"Impossible de détruire le fichier.\nErreur : {e}") + messagebox.showerror("Erreur", f"Erreur : {e}") + + def calculate_hash(self): + filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ UN FICHIER") + 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) + + self.hash_result_var.set(f"SHA-256: {sha256_hash.hexdigest()}") + except Exception as e: + self.hash_result_var.set(f"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) + + # --- VUE : QUARANTAINE --- + def init_quarantine_view(self): + frame = ctk.CTkFrame(self, fg_color="transparent") + 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") + else: + self.quarantine_listbox.insert("end", f" ⚠️ {len(files)} menace(s) :\n\n") + for f in files: + size_kb = os.path.getsize(f) / 1024 + self.quarantine_listbox.insert("end", f" -> {os.path.basename(f)} ({size_kb:.1f} KB)\n") + + 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) + except: pass + self.refresh_quarantine_list() # --- VUE : MISE À JOUR (OTA) --- 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=28, weight="bold")).pack(anchor="w", pady=(0, 20)) - ctk.CTkLabel(frame, text="Restez synchronisé avec le serveur officiel 7ka1 Git.", text_color="#9CA3AF").pack(anchor="w", pady=(0, 20)) + 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 = ctk.CTkButton(frame, text="⬇️ Forcer la mise à jour depuis Gitea", command=self.run_software_update, fg_color="#8B5CF6", hover_color="#7C3AED", height=45) - self.btn_update.pack(anchor="w") + 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=150, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827") + 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.configure(state="disabled") - self.update_console.insert("end", "[*] Négociation avec git.7ka1.com...\n", "info") + 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") try: current_file_path = os.path.abspath(__file__) urllib.request.urlretrieve(UPDATE_URL, current_file_path) - self.update_console.insert("end", "[+] Code source remplacé avec succès !\n", "success") - messagebox.showinfo("Mise à jour Appliquée", "Redémarrage requis pour initialiser la nouvelle version.") + 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() - except Exception as e: - self.update_console.insert("end", f"[-] Échec de la liaison avec le serveur : {e}\n", "danger") - self.btn_update.configure(state="normal") + self.update_console.insert("end", f"{self.get_time_prefix()}[-] Échec : {e}\n", "danger") + self.btn_update_soft.configure(state="normal") if __name__ == "__main__": app = Antivirus7LnA()