Actualiser 7lna.py

This commit is contained in:
2026-03-05 10:18:26 +00:00
parent e1982dba0a
commit 51788eab62

328
7lna.py
View File

@@ -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()