178 lines
5.6 KiB
Python
178 lines
5.6 KiB
Python
import sys
|
|
import os
|
|
import subprocess
|
|
import platform
|
|
|
|
|
|
def is_windows() -> bool:
|
|
return sys.platform == "win32"
|
|
|
|
|
|
def is_linux() -> bool:
|
|
return sys.platform.startswith("linux")
|
|
|
|
|
|
def get_config_dir() -> str:
|
|
if is_windows():
|
|
base = os.environ.get("APPDATA", os.path.expanduser("~"))
|
|
path = os.path.join(base, "WGSecure")
|
|
else:
|
|
path = os.path.join(os.path.expanduser("~"), ".wgsecure")
|
|
os.makedirs(path, exist_ok=True)
|
|
return path
|
|
|
|
|
|
def get_wg_config_dir() -> str:
|
|
if is_windows():
|
|
base = os.environ.get("PROGRAMDATA", r"C:\ProgramData")
|
|
return os.path.join(base, "WireGuard")
|
|
return "/etc/wireguard"
|
|
|
|
|
|
def has_root_privileges() -> bool:
|
|
if is_windows():
|
|
try:
|
|
import ctypes
|
|
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
except Exception:
|
|
return False
|
|
return os.geteuid() == 0
|
|
|
|
|
|
def run_command(cmd: list[str], timeout: int = 10) -> tuple[int, str, str]:
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
)
|
|
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
|
except subprocess.TimeoutExpired:
|
|
return -1, "", "Timeout"
|
|
except FileNotFoundError:
|
|
return -1, "", f"Commande introuvable : {cmd[0]}"
|
|
except Exception as e:
|
|
return -1, "", str(e)
|
|
|
|
|
|
def run_privileged(cmd: list[str], timeout: int = 60) -> tuple[int, str, str]:
|
|
if is_windows():
|
|
return run_command(cmd, timeout)
|
|
if has_root_privileges():
|
|
return run_command(cmd, timeout)
|
|
|
|
# 1. sudo -n (NOPASSWD configuré dans sudoers — sans aucun dialogue)
|
|
code, out, err = run_command(["sudo", "-n"] + cmd, timeout)
|
|
if code == 0:
|
|
return code, out, err
|
|
# Si sudo a tourné mais la commande a échoué (pas un problème d'auth), retourner l'erreur
|
|
if code != 1 or ("password" not in err.lower() and "passwd" not in err.lower()):
|
|
if "sudo:" not in err.lower() and code not in (-1,):
|
|
return code, out, err
|
|
|
|
# 2. sudo avec programme askpass graphique (pas de TTY dans une app Qt)
|
|
askpass = _find_askpass()
|
|
if askpass:
|
|
env = os.environ.copy()
|
|
env["SUDO_ASKPASS"] = askpass
|
|
try:
|
|
result = subprocess.run(
|
|
["sudo", "-A"] + cmd,
|
|
capture_output=True, text=True,
|
|
timeout=timeout, env=env,
|
|
)
|
|
if result.returncode == 0:
|
|
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
|
except (subprocess.TimeoutExpired, Exception):
|
|
pass
|
|
|
|
# 3. pkexec — dialogue graphique polkit (GNOME/KDE)
|
|
code, out, err = run_command(["pkexec"] + cmd, timeout)
|
|
if code != -1 or "introuvable" not in err:
|
|
return code, out, err
|
|
|
|
return (
|
|
-1, "",
|
|
"Élévation de privilèges impossible.\n"
|
|
"Exécutez make setup-sudoers pour configurer wg-quick sans dialogue.\n"
|
|
"Ou manuellement :\n"
|
|
f" sudo bash -c \"echo '{os.environ.get('USER','<user>')} ALL=(ALL) NOPASSWD: /usr/bin/wg-quick'"
|
|
" > /etc/sudoers.d/wgsecure && chmod 440 /etc/sudoers.d/wgsecure\""
|
|
)
|
|
|
|
|
|
def _find_askpass() -> str:
|
|
"""Cherche un programme askpass graphique disponible."""
|
|
candidates = [
|
|
os.environ.get("SUDO_ASKPASS", ""),
|
|
"/usr/lib/openssh/gnome-ssh-askpass",
|
|
"/usr/bin/ssh-askpass",
|
|
"/usr/bin/x11-ssh-askpass",
|
|
"/usr/libexec/ssh-askpass",
|
|
]
|
|
for p in candidates:
|
|
if p and os.path.isfile(p) and os.access(p, os.X_OK):
|
|
return p
|
|
# Cherche aussi via shutil
|
|
import shutil
|
|
for name in ("ssh-askpass", "x11-ssh-askpass", "gnome-ssh-askpass"):
|
|
found = shutil.which(name)
|
|
if found:
|
|
return found
|
|
return ""
|
|
|
|
|
|
def wg_available() -> bool:
|
|
code, _, _ = run_command(["wg", "--version"])
|
|
return code == 0
|
|
|
|
|
|
def wg_quick_available() -> bool:
|
|
if is_windows():
|
|
code, _, _ = run_command(["wireguard", "--help"])
|
|
return code == 0
|
|
code, _, _ = run_command(["wg-quick", "--help"])
|
|
return code == 0
|
|
|
|
|
|
def install_desktop_entry(icon_png_path: str) -> None:
|
|
"""Installe l'entrée .desktop et l'icône PNG pour la barre des tâches GNOME/KDE.
|
|
Appelé une fois au démarrage ; sans effet sur Windows."""
|
|
if is_windows():
|
|
return
|
|
try:
|
|
# Icône dans le thème hicolor
|
|
icon_dir = os.path.expanduser("~/.local/share/icons/hicolor/256x256/apps")
|
|
os.makedirs(icon_dir, exist_ok=True)
|
|
dest_icon = os.path.join(icon_dir, "wgsecure.png")
|
|
import shutil
|
|
shutil.copy2(icon_png_path, dest_icon)
|
|
|
|
# Fichier .desktop
|
|
app_dir = os.path.expanduser("~/.local/share/applications")
|
|
os.makedirs(app_dir, exist_ok=True)
|
|
script = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "..", "..", "main.py")
|
|
)
|
|
desktop = os.path.join(app_dir, "wgsecure.desktop")
|
|
content = (
|
|
"[Desktop Entry]\n"
|
|
"Type=Application\n"
|
|
"Name=WGSecure\n"
|
|
"Comment=WireGuard GUI avec MFA TOTP\n"
|
|
f"Exec=python3 {script}\n"
|
|
"Icon=wgsecure\n"
|
|
"Categories=Network;Security;\n"
|
|
"StartupWMClass=main\n"
|
|
"Terminal=false\n"
|
|
)
|
|
with open(desktop, "w") as f:
|
|
f.write(content)
|
|
|
|
# Mettre à jour le cache d'icônes si disponible
|
|
run_command(["gtk-update-icon-cache", "-f", "-t",
|
|
os.path.expanduser("~/.local/share/icons/hicolor")])
|
|
except Exception:
|
|
pass
|