179 lines
5.2 KiB
Python
179 lines
5.2 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
AZA Desktop – Windows-Firewall-Handling.
|
|||
|
|
Minimale lokale Regel fuer Backend auf 127.0.0.1:8000.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
import subprocess
|
|||
|
|
|
|||
|
|
_RULE_NAME = "AZA Desktop - Lokale Kommunikation"
|
|||
|
|
_BACKEND_PORT = 8000
|
|||
|
|
|
|||
|
|
|
|||
|
|
def add_firewall_rule_if_needed() -> tuple[bool, str]:
|
|||
|
|
"""
|
|||
|
|
Legt eine minimale Windows-Firewall-Regel fuer das lokale AZA-Backend an.
|
|||
|
|
Nur fuer aza_desktop.exe, TCP 8000, Remote 127.0.0.1 (localhost).
|
|||
|
|
Gibt (erfolg, meldung) zurueck.
|
|||
|
|
"""
|
|||
|
|
if sys.platform != "win32":
|
|||
|
|
return True, ""
|
|||
|
|
|
|||
|
|
exe_path = ""
|
|||
|
|
if getattr(sys, "frozen", False):
|
|||
|
|
exe_path = sys.executable
|
|||
|
|
else:
|
|||
|
|
# Dev: Basis-Pfad ermitteln
|
|||
|
|
base = os.path.dirname(os.path.abspath(__file__))
|
|||
|
|
for _ in range(5):
|
|||
|
|
candidate = os.path.join(base, "aza_desktop.exe")
|
|||
|
|
if os.path.isfile(candidate):
|
|||
|
|
exe_path = candidate
|
|||
|
|
break
|
|||
|
|
parent = os.path.dirname(base)
|
|||
|
|
if parent == base:
|
|||
|
|
break
|
|||
|
|
base = parent
|
|||
|
|
|
|||
|
|
if not exe_path or not os.path.isfile(exe_path):
|
|||
|
|
return False, "aza_desktop.exe nicht gefunden"
|
|||
|
|
|
|||
|
|
exe_path = os.path.normpath(exe_path)
|
|||
|
|
|
|||
|
|
# Regel loeschen falls vorhanden (idempotent)
|
|||
|
|
try:
|
|||
|
|
subprocess.run(
|
|||
|
|
[
|
|||
|
|
"netsh", "advfirewall", "firewall", "delete", "rule",
|
|||
|
|
f"name={_RULE_NAME}",
|
|||
|
|
],
|
|||
|
|
capture_output=True,
|
|||
|
|
timeout=10,
|
|||
|
|
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0,
|
|||
|
|
)
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# Regel anlegen
|
|||
|
|
try:
|
|||
|
|
result = subprocess.run(
|
|||
|
|
[
|
|||
|
|
"netsh", "advfirewall", "firewall", "add", "rule",
|
|||
|
|
f"name={_RULE_NAME}",
|
|||
|
|
"dir=in",
|
|||
|
|
"action=allow",
|
|||
|
|
f"program={exe_path}",
|
|||
|
|
f"localport={_BACKEND_PORT}",
|
|||
|
|
"protocol=tcp",
|
|||
|
|
"remoteip=127.0.0.1",
|
|||
|
|
],
|
|||
|
|
capture_output=True,
|
|||
|
|
text=True,
|
|||
|
|
timeout=10,
|
|||
|
|
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0,
|
|||
|
|
)
|
|||
|
|
if result.returncode == 0:
|
|||
|
|
return True, "Firewall-Regel angelegt"
|
|||
|
|
err = (result.stderr or result.stdout or "").strip()
|
|||
|
|
return False, err or f"Rueckcode {result.returncode}"
|
|||
|
|
except subprocess.TimeoutExpired:
|
|||
|
|
return False, "Zeitueberschreitung"
|
|||
|
|
except Exception as e:
|
|||
|
|
return False, str(e)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def check_firewall_rule() -> dict:
|
|||
|
|
"""
|
|||
|
|
Prueft ohne Adminrechte, ob die erwartete Firewall-Regel existiert.
|
|||
|
|
Gibt dict mit keys: exists, program_ok, port_ok, protocol_ok, remote_ok, detail
|
|||
|
|
"""
|
|||
|
|
result = {
|
|||
|
|
"exists": False,
|
|||
|
|
"program_ok": None,
|
|||
|
|
"port_ok": None,
|
|||
|
|
"protocol_ok": None,
|
|||
|
|
"remote_ok": None,
|
|||
|
|
"detail": "",
|
|||
|
|
}
|
|||
|
|
if sys.platform != "win32":
|
|||
|
|
result["detail"] = "Nur unter Windows relevant"
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
proc = subprocess.run(
|
|||
|
|
[
|
|||
|
|
"netsh", "advfirewall", "firewall", "show", "rule",
|
|||
|
|
f"name={_RULE_NAME}", "verbose",
|
|||
|
|
],
|
|||
|
|
capture_output=True,
|
|||
|
|
text=True,
|
|||
|
|
timeout=10,
|
|||
|
|
creationflags=(
|
|||
|
|
subprocess.CREATE_NO_WINDOW
|
|||
|
|
if hasattr(subprocess, "CREATE_NO_WINDOW")
|
|||
|
|
else 0
|
|||
|
|
),
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
result["detail"] = f"Pruefung nicht moeglich: {e}"
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
stdout = proc.stdout or ""
|
|||
|
|
if proc.returncode != 0 or _RULE_NAME not in stdout:
|
|||
|
|
result["detail"] = "Regel nicht vorhanden"
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
result["exists"] = True
|
|||
|
|
|
|||
|
|
lower = stdout.lower()
|
|||
|
|
|
|||
|
|
if "tcp" in lower:
|
|||
|
|
result["protocol_ok"] = True
|
|||
|
|
else:
|
|||
|
|
result["protocol_ok"] = False
|
|||
|
|
|
|||
|
|
if str(_BACKEND_PORT) in stdout:
|
|||
|
|
result["port_ok"] = True
|
|||
|
|
else:
|
|||
|
|
result["port_ok"] = False
|
|||
|
|
|
|||
|
|
if "127.0.0.1" in stdout or "localsubnet" in lower:
|
|||
|
|
result["remote_ok"] = True
|
|||
|
|
else:
|
|||
|
|
result["remote_ok"] = False
|
|||
|
|
|
|||
|
|
if "aza_desktop" in lower:
|
|||
|
|
result["program_ok"] = True
|
|||
|
|
else:
|
|||
|
|
result["program_ok"] = False
|
|||
|
|
|
|||
|
|
problems = []
|
|||
|
|
if not result["protocol_ok"]:
|
|||
|
|
problems.append("Protokoll")
|
|||
|
|
if not result["port_ok"]:
|
|||
|
|
problems.append("Port")
|
|||
|
|
if not result["remote_ok"]:
|
|||
|
|
problems.append("Remote-IP")
|
|||
|
|
if not result["program_ok"]:
|
|||
|
|
problems.append("Programm")
|
|||
|
|
|
|||
|
|
if problems:
|
|||
|
|
result["detail"] = "Regel vorhanden, Details abweichend: " + ", ".join(problems)
|
|||
|
|
else:
|
|||
|
|
result["detail"] = "Regel korrekt (TCP 8000, localhost, programmgebunden)"
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_firewall_hint_text() -> str:
|
|||
|
|
"""Ruiger Hinweistext fuer den Nutzer bei Firewall-Popup oder Backend-Problem."""
|
|||
|
|
return (
|
|||
|
|
"Windows hat eine Rueckfrage zur lokalen Netzwerkkommunikation von AZA angezeigt.\n\n"
|
|||
|
|
"AZA verwendet lokal laufende Komponenten auf diesem Computer. "
|
|||
|
|
"Die Rueckfrage betrifft die interne Kommunikation der App.\n\n"
|
|||
|
|
"Fuer den lokalen Betrieb von AZA koennen Sie fortfahren bzw. "
|
|||
|
|
"den Zugriff fuer private Netzwerke erlauben."
|
|||
|
|
)
|