import customtkinter as ctk from tkinter import filedialog, messagebox import os import threading import subprocess import urllib.request import urllib.error import time 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 # ========================================== # CONFIGURATION GLOBALE # ========================================== 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") # ========================================== # UTILITAIRES SYSTÈME # ========================================== def send_desktop_notification(title, message, is_critical=False): try: urgency = 'critical' if is_critical else 'normal' subprocess.Popen(['notify-send', '-u', urgency, '-a', '7LnA Security', title, message]) except Exception: pass class RealTimeShieldHandler(FileSystemEventHandler): def __init__(self, app_instance): self.app = app_instance def on_created(self, event): if not event.is_directory: time.sleep(1.5) self.app.trigger_realtime_scan(event.src_path) # ========================================== # APPLICATION PRINCIPALE # ========================================== class Antivirus7LnA(ctk.CTk): def __init__(self): super().__init__() self.title("7LnA Security Suite - V10.1 Quantum 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.zero_usb_mode = False # <--- AJOUT : État initial du mode Zero USB self.check_dependencies() self.setup_ui() threading.Thread(target=self.monitor_usb_drives, daemon=True).start() def check_dependencies(self): try: subprocess.run(['clamscan', '--version'], capture_output=True, check=True) self.clamav_installed = True except: self.clamav_installed = False def setup_ui(self): self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) # --- 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(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)) 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_quarantine = self.create_nav_button("📦 Quarantaine", 6, "quarantine") self.btn_schedule = self.create_nav_button("📅 Planification", 7, "schedule") self.btn_history = self.create_nav_button("📜 Rapports", 8, "history") self.btn_update = self.create_nav_button("🔄 Mise à jour", 9, "update") self.version_label = ctk.CTkLabel(self.sidebar, text="v10.1 - Quantum", text_color="#6B7280", font=ctk.CTkFont(weight="bold")) self.version_label.grid(row=10, column=0, pady=20, sticky="s") self.views = {} self.init_dashboard_view() self.init_scanner_view() self.init_realtime_view() self.init_audit_view() self.init_tools_view() self.init_quarantine_view() self.init_schedule_view() self.init_history_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=15), height=40, command=lambda: self.select_view(view_name)) btn.grid(row=row, column=0, padx=20, pady=5, sticky="ew") return btn def select_view(self, view_name): for view in self.views.values(): 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 == "history": self.refresh_history_list() def setup_console_tags(self, console): 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] ") 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: pass # --- 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: self.after(0, self.prompt_usb_scan, os.path.join(media_dir, mount), mount) known_mounts = current_mounts except: pass # <--- MODIFICATION : Logique Zero USB intégrée def prompt_usb_scan(self, path, name): if self.zero_usb_mode: send_desktop_notification("⚡ Zero USB Actif", f"Analyse automatique forcée pour : {name}", is_critical=True) self.select_view("scanner") threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start() else: 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 l'analyser ?"): 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) 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") # <--- AJOUT : Switch pour activer/désactiver le Mode Zero USB self.zero_usb_switch = ctk.CTkSwitch(frame, text="🛡️ Mode Zero USB (Scan auto des clés USB)", command=self.toggle_zero_usb, font=ctk.CTkFont(size=16, weight="bold"), progress_color="#DC2626") # Rouge pour indiquer un mode agressif self.zero_usb_switch.pack(pady=20, anchor="w", padx=20) # <--- AJOUT : Fonction de bascule pour le Switch def toggle_zero_usb(self): self.zero_usb_mode = self.zero_usb_switch.get() etat = "ACTIVÉ" if self.zero_usb_mode else "DÉSACTIVÉ" send_desktop_notification("Paramètre de Sécurité", f"Mode Zero USB {etat}") # --- FONCTION D'AUTHENTIFICATION GLOBALE --- def get_sudo_password(self, callback_func, title="Sécurité Administrateur", msg="Privilèges requis pour cette action.\nEntrez votre mot de passe session :"): dialog = ctk.CTkToplevel(self) dialog.title(title) dialog.geometry("400x230") ctk.CTkLabel(dialog, text="⚠️ Privilèges Requis", font=ctk.CTkFont(size=20, weight="bold"), text_color="#F59E0B").pack(pady=(20, 5)) ctk.CTkLabel(dialog, text=msg, justify="center").pack(pady=5) pwd_entry = ctk.CTkEntry(dialog, show="*", width=250) pwd_entry.pack(pady=10) def on_submit(event=None): pwd = pwd_entry.get() dialog.destroy() if pwd: callback_func(pwd) ctk.CTkButton(dialog, text="Confirmer", command=on_submit, fg_color="#DC2626", hover_color="#991B1B").pack(pady=10) dialog.bind("", on_submit) dialog.wait_visibility() dialog.attributes("-topmost", True) dialog.grab_set() pwd_entry.focus_set() # --- VUE : SCANNER MANUEL --- def init_scanner_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") 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)) 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), 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), pady=5, sticky="ew") self.btn_db_update = ctk.CTkButton(frame, text="🔄 MaJ Signatures", command=self.update_virus_db_prompt, height=45, fg_color="#059669", hover_color="#047857") self.btn_db_update.grid(row=1, column=2, padx=(5, 0), pady=5, sticky="ew") 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=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=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 V10.1 prêt...\n", "info") def run_rootkit_scan(self): self.get_sudo_password(lambda pwd: threading.Thread(target=self._exec_rootkit, args=(pwd,), daemon=True).start()) def _exec_rootkit(self, pwd): self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de rkhunter (cela peut prendre du temps)...\n", "info") self.scan_progress.start() try: cmd = ['sudo', '-S', 'rkhunter', '-c', '--sk', '--report-warnings-only'] process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) process.stdin.write(pwd + '\n') process.stdin.flush() for line in iter(process.stdout.readline, ''): if "incorrect password" in line.lower() or "try again" in line.lower(): self.scan_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé par le système.\n", "danger") process.terminate() break else: self.scan_console.insert("end", line) self.scan_console.see("end") process.wait() if process.returncode in [0, 1]: 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é.\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_prompt(self): self.get_sudo_password(lambda pwd: threading.Thread(target=self._run_freshclam, args=(pwd,), daemon=True).start(), msg="La mise à jour des signatures ClamAV nécessite\nles droits sudo. Entrez votre mot de passe :") def _run_freshclam(self, pwd): self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de la mise à jour (freshclam)...\n", "info") self.scan_progress.start() self.btn_db_update.configure(state="disabled") try: cmd = ['sudo', '-S', 'freshclam'] process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) process.stdin.write(pwd + '\n') process.stdin.flush() for line in iter(process.stdout.readline, ''): if "incorrect password" in line.lower() or "try again" in line.lower(): self.scan_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé.\n", "danger") process.terminate() break else: 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()}[+] Signatures virales mises à jour avec succès.\n", "success") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur freshclam : {e}\n", "danger") finally: self.scan_progress.stop() 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() # --- VUE : BOUCLIER TEMPS RÉEL --- def init_realtime_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["shield"] = frame 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=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) self.btn_toggle_shield.pack(side="right") 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) def toggle_shield(self): if not self.shield_active: self.shield_observer = Observer() self.shield_observer.schedule(RealTimeShieldHandler(self), WATCH_FOLDER, recursive=False) 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{self.get_time_prefix()}[+] BOUCLIER ARMÉ : {WATCH_FOLDER}\n", "success") else: self.shield_observer.stop() self.shield_active = False self.btn_toggle_shield.configure(text="Démarrer la Surveillance", fg_color="#059669") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[-] Bouclier désactivé.\n", "warning") 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 --- def run_clamav_scan(self, path, is_dir, console): 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() try: cmd = ['clamscan', '-i', '--no-summary', f'--move={QUARANTINE_DIR}'] if is_dir: cmd.append('-r') cmd.append(path) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) infected = 0 for line in process.stdout: clean_line = line.strip() if "FOUND" in clean_line: threat_name = clean_line.split(":")[-1].replace("FOUND", "").strip() console.insert("end", f"{self.get_time_prefix()}☠️ MENACE DÉTECTÉE : {clean_line}\n", "danger") infected += 1 self.log_threat_to_history(path, threat_name) 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: send_desktop_notification("🚨 VIRUS NEUTRALISÉ", f"{infected} menace(s) trouvée(s).", is_critical=True) except Exception as e: console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: if console == self.scan_console: self.scan_progress.stop() console.see("end") # --- VUE : AUDIT & PARE-FEU (AVEC WIFI GUARD V10.1) --- def init_audit_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") 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 UFW", command=self.check_firewall, fg_color="#4B5563", hover_color="#374151", height=40).pack(side="left", padx=(0, 10)) ctk.CTkButton(btn_frame, text="📡 WiFi Guard (Scanner Intrus)", command=self.run_wifi_guard_prompt, fg_color="#059669", hover_color="#047857", 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") def run_audit_thread(self): self.audit_console.delete("0.0", "end") threading.Thread(target=self.perform_audit, daemon=True).start() def perform_audit(self): try: self.audit_console.insert("end", subprocess.check_output(['ss', '-tuln'], text=True)) except: pass try: res = subprocess.check_output(['ps', '-eo', 'pid,user,%cpu,%mem,cmd', '--sort=-%cpu'], text=True) self.audit_console.insert("end", "\n" + "\n".join(res.split('\n')[:11]) + "\n") except: pass def check_firewall(self): self.audit_console.delete("0.0", "end") 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") else: self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\n") except: pass # ----- LOGIQUE WIFI GUARD ----- def run_wifi_guard_prompt(self): self.get_sudo_password(lambda pwd: threading.Thread(target=self._exec_wifi_guard, args=(pwd,), daemon=True).start(), title="WiFi Guard", msg="Le scan réseau profond nécessite les droits sudo.\nEntrez votre mot de passe session :") def _exec_wifi_guard(self, pwd): self.audit_console.delete("0.0", "end") self.audit_console.insert("end", f"{self.get_time_prefix()}[*] Démarrage du scan réseau (arp-scan)... \n", "info") if not shutil.which("arp-scan"): self.audit_console.insert("end", f"{self.get_time_prefix()}[!] Erreur : arp-scan introuvable.\n", "danger") return try: cmd = ['sudo', '-S', 'arp-scan', '--localnet', '--ignoredups'] process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) process.stdin.write(pwd + '\n') process.stdin.flush() count = 0 for line in iter(process.stdout.readline, ''): if any(x in line.lower() for x in ["permission denied", "starting arp-scan", "packets received", "ending arp-scan", "[sudo]"]): continue if "incorrect password" in line.lower() or "try again" in line.lower(): self.audit_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé.\n", "danger") break if line.strip() and (line[0].isdigit()): self.audit_console.insert("end", f" 📱 Appareil trouvé : {line.strip()}\n", "success") count += 1 self.audit_console.see("end") process.wait() self.audit_console.insert("end", f"\n{self.get_time_prefix()}[+] Scan terminé. {count} appareils détectés sur votre WiFi.\n", "info") if count > 15: send_desktop_notification("WiFi Guard", f"Attention : Beaucoup d'appareils ({count}) sont connectés à votre réseau.", is_critical=True) except Exception as e: self.audit_console.insert("end", f"❌ Erreur : {e}\n", "danger") # --- VUE : OUTILS AVANCÉS (V10) --- def init_tools_view(self): 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)) # Vérificateur de Fuites de Données (OSINT) breach_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) breach_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(breach_card, text="🌐 Vérificateur de Fuites (OSINT)", font=ctk.CTkFont(size=18, weight="bold"), text_color="#A855F7").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkLabel(breach_card, text="Vérifiez si votre adresse e-mail a été compromise dans une fuite de données (Dark Web).", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) input_frame = ctk.CTkFrame(breach_card, fg_color="transparent") input_frame.pack(fill="x", padx=20, pady=5) self.email_entry = ctk.CTkEntry(input_frame, placeholder_text="Entrez votre e-mail...", width=300) self.email_entry.pack(side="left", padx=(0, 10)) ctk.CTkButton(input_frame, text="Vérifier l'e-mail", fg_color="#9333EA", hover_color="#7E22CE", command=self.run_breach_check).pack(side="left") self.breach_result_var = ctk.StringVar(value="") self.breach_result_label = ctk.CTkLabel(breach_card, textvariable=self.breach_result_var, text_color="#D1D5DB", justify="left") self.breach_result_label.pack(anchor="w", padx=20, pady=10) # Nettoyeur 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.CTkButton(clean_card, text="Vider le Cache et Corbeille", fg_color="#059669", hover_color="#047857", command=self.run_cleaner).pack(anchor="w", padx=20, pady=10) # Destructeur 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", font=ctk.CTkFont(size=18, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkButton(shred_card, text="Détruire un fichier", fg_color="#DC2626", hover_color="#991B1B", command=self.run_shredder).pack(anchor="w", padx=20, pady=10) # --- LOGIQUE OSINT --- def run_breach_check(self): email = self.email_entry.get().strip() if not email or "@" not in email: self.breach_result_var.set("❌ Veuillez entrer une adresse e-mail valide.") self.breach_result_label.configure(text_color="#EF4444") return self.breach_result_var.set("⏳ Interrogation des bases de données...") self.breach_result_label.configure(text_color="#3B82F6") threading.Thread(target=self._exec_breach_check, args=(email,), daemon=True).start() def _exec_breach_check(self, email): try: url = f"https://api.xposedornot.com/v1/check-email/{email}" req = urllib.request.Request(url, headers={'User-Agent': '7LnA-Security-Suite'}) try: with urllib.request.urlopen(req) as response: data = json.loads(response.read().decode()) if "breaches" in data and data["breaches"]: breaches = data["breaches"][0] breach_count = len(breaches) breach_names = ", ".join(breaches[:4]) suffix = "..." if breach_count > 4 else "" self.breach_result_var.set(f"🚨 DANGER : E-mail trouvé dans {breach_count} fuite(s) !\nSources connues : {breach_names}{suffix}\nChangez vos mots de passe immédiatement.") self.breach_result_label.configure(text_color="#EF4444") else: self.breach_result_var.set("✅ Bonne nouvelle : Aucune fuite détectée pour cet e-mail.") self.breach_result_label.configure(text_color="#10B981") except urllib.error.HTTPError as e: if e.code == 404: self.breach_result_var.set("✅ Bonne nouvelle : Aucune fuite détectée pour cet e-mail.") self.breach_result_label.configure(text_color="#10B981") else: self.breach_result_var.set(f"❌ Erreur serveur lors de la vérification ({e.code}).") self.breach_result_label.configure(text_color="#F59E0B") except Exception as e: self.breach_result_var.set("❌ Impossible de contacter le serveur d'OSINT. Vérifiez votre connexion.") self.breach_result_label.configure(text_color="#F59E0B") def run_cleaner(self): paths_to_clean = [os.path.expanduser("~/.local/share/Trash/files"), os.path.expanduser("~/.cache/thumbnails")] 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: if os.path.isfile(item_path): os.remove(item_path) elif os.path.isdir(item_path): shutil.rmtree(item_path) except: pass messagebox.showinfo("Succès", "Nettoyage terminé.") def run_shredder(self): filepath = filedialog.askopenfilename() if not filepath: return if messagebox.askyesno("DANGER", f"Détruire DÉFINITIVEMENT ?\n\n{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.") except: pass # ----- PLANIFICATION ----- 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 via les tâches 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 (Dossier Téléchargements)", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(10, 5)) time_frame = ctk.CTkFrame(card, fg_color="transparent") time_frame.pack(pady=(5, 15)) ctk.CTkLabel(time_frame, text="Heure d'exécution :").pack(side="left", padx=5) self.cron_h_var = ctk.StringVar(value="12") self.cron_m_var = ctk.StringVar(value="00") ctk.CTkComboBox(time_frame, values=[f"{i:02d}" for i in range(24)], variable=self.cron_h_var, width=70).pack(side="left", padx=5) ctk.CTkLabel(time_frame, text="h").pack(side="left") ctk.CTkComboBox(time_frame, values=["00", "15", "30", "45"], variable=self.cron_m_var, width=70).pack(side="left", padx=5) ctk.CTkButton(card, text="✅ Activer le Scan Quotidien", fg_color="#2563EB", hover_color="#1D4ED8", command=self.setup_cronjob).pack(pady=5) ctk.CTkButton(card, text="❌ Désactiver la planification", fg_color="#4B5563", hover_color="#374151", command=self.remove_cronjob).pack(pady=5) def setup_cronjob(self): h = self.cron_h_var.get() m = self.cron_m_var.get() cron_command = f"{int(m)} {int(h)} * * * /usr/bin/clamscan -r --move={QUARANTINE_DIR} {WATCH_FOLDER} > /dev/null 2>&1" try: os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}"; echo "{cron_command}") | crontab -') messagebox.showinfo("Succès", f"Scan configuré tous les jours à {h}:{m}.") except: 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 # --- 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 ctk.CTkLabel(frame, text="📜 Registre des Menaces", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) 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.\n") else: for entry in reversed(data): self.history_console.insert("end", f"[{entry['date']}] ☠️ {entry['threat']}\n -> Fichier : {entry['file']}\n\n") except: pass # --- 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: for f in files: self.quarantine_listbox.insert("end", f" -> {os.path.basename(f)}\n") def empty_quarantine(self): files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) if files and 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 --- 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)) 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) def run_software_update(self): self.btn_update_soft.configure(state="disabled") self.update_console.insert("end", "[*] Négociation avec serveur...\n") try: urllib.request.urlretrieve(UPDATE_URL, os.path.abspath(__file__)) self.update_console.insert("end", "[+] Mise à jour réussie !\n") messagebox.showinfo("Succès", "Redémarrage requis.") self.destroy() except: self.update_console.insert("end", "[-] Échec.\n") self.btn_update_soft.configure(state="normal") if __name__ == "__main__": app = Antivirus7LnA() app.mainloop()