fix(satellite): FritzBox-Tastenbestaetigung — Token im SOAP-Header mitschicken
An der Kellerbox kam nach dem Tastendruck "stopped" statt "authenticated". Laut AVM "TR-064 Support – Authentication" (Kap. 6.4) muss der Token aus X_AVM-DE_Auth SetConfig(start) bei GetState UND beim Wiederholen der Aktion als <avm:token> im SOAP-Header stehen — ohne ihn ordnet die Box die Bestaetigung nicht unserem Vorgang zu. - _tr064(token=…) setzt den Header. - GetState und die wiederholte Aktion laufen mit dem Token; Stop ebenso. - Zustaende werden geloggt und erklaert (stopped/blocked/ anotherauthprocess/failure); 867 (gesperrt, bis 60 min) und 868 (beschaeftigt, ~2 min) mit verstaendlicher Meldung. Getestet gegen eine nachgebaute Box, die sich wie in AVMs Doku verhaelt (ohne Token "stopped", mit Token "authenticated"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+30
-9
@@ -1213,7 +1213,7 @@ def _do_snmp_info(params: dict) -> dict:
|
||||
# schlanker Reader. Braucht FritzBox-Login (Credential-Store, Typ 'fritzbox').
|
||||
|
||||
def _tr064(ip: str, user: str, pw: str, service: str, control: str,
|
||||
action: str, args: Optional[dict] = None) -> dict:
|
||||
action: str, args: Optional[dict] = None, token: str = "") -> dict:
|
||||
"""Ein TR-064-SOAP-Call. Gibt {ok, fields|error}. fields = alle <NewX>-Tags."""
|
||||
import requests
|
||||
from requests.auth import HTTPDigestAuth
|
||||
@@ -1221,7 +1221,14 @@ def _tr064(ip: str, user: str, pw: str, service: str, control: str,
|
||||
envelope = (
|
||||
'<?xml version="1.0"?>'
|
||||
'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" '
|
||||
's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>'
|
||||
's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
|
||||
# Zusatzbestaetigung: der Token aus X_AVM-DE_Auth SetConfig(start) MUSS
|
||||
# bei GetState und beim Wiederholen der Aktion im SOAP-Header stehen
|
||||
# (AVM "TR-064 Support – Authentication", Kap. 6.4) — sonst ordnet die
|
||||
# Box den Tastendruck nicht zu und meldet "stopped".
|
||||
+ (f'<s:Header><avm:token xmlns:avm="avm.de" s:mustUnderstand="1">{token}</avm:token></s:Header>'
|
||||
if token else '')
|
||||
+ '<s:Body>'
|
||||
f'<u:{action} xmlns:u="{service}">{body}</u:{action}>'
|
||||
'</s:Body></s:Envelope>'
|
||||
)
|
||||
@@ -1374,18 +1381,27 @@ def _fb_second_factor(ip: str, user: str, pw: str) -> dict:
|
||||
if not st.get("ok"):
|
||||
return {"ok": False, "error": f"Bestaetigung nicht startbar: {st.get('error')}"}
|
||||
methods = st["fields"].get("NewMethods", "")
|
||||
token = st["fields"].get("NewToken", "")
|
||||
logger.info("[fritzbox] %s: warte bis %.0fs auf Bestaetigung am Geraet (%s)", ip, FB_BUTTON_WAIT_SEC, methods)
|
||||
deadline = time.time() + FB_BUTTON_WAIT_SEC
|
||||
state = st["fields"].get("NewState", "")
|
||||
reasons = {"stopped": "die Box hat den Vorgang beendet, ohne Bestaetigung (Zeit abgelaufen?)",
|
||||
"blocked": "zu viele Versuche — die Box sperrt bis zu 60 Minuten",
|
||||
"anotherauthprocess": "es laeuft schon eine Bestaetigung fuer einen anderen Benutzer",
|
||||
"failure": "interner Fehler der Box", "disabled": "die Zusatzbestaetigung ist aus"}
|
||||
while time.time() < deadline:
|
||||
if state == "authenticated":
|
||||
return {"ok": True}
|
||||
if state in ("blocked", "failure", "stopped"):
|
||||
return {"ok": False, "error": f"Bestaetigung abgebrochen (Box meldet '{state}')."}
|
||||
logger.info("[fritzbox] %s: bestaetigt", ip)
|
||||
return {"ok": True, "token": token}
|
||||
if state in reasons and state != "disabled":
|
||||
return {"ok": False, "error": f"Bestaetigung nicht erfolgt — Box meldet '{state}': {reasons[state]}."}
|
||||
time.sleep(2)
|
||||
g = _tr064(ip, user, pw, auth["type"], auth["control"], "GetState")
|
||||
state = g.get("fields", {}).get("NewState", state) if g.get("ok") else state
|
||||
_tr064(ip, user, pw, auth["type"], auth["control"], "SetConfig", {"NewAction": "stop"})
|
||||
g = _tr064(ip, user, pw, auth["type"], auth["control"], "GetState", token=token)
|
||||
new = g.get("fields", {}).get("NewState", state) if g.get("ok") else state
|
||||
if new != state:
|
||||
logger.info("[fritzbox] %s: Bestaetigung %s → %s", ip, state, new)
|
||||
state = new
|
||||
_tr064(ip, user, pw, auth["type"], auth["control"], "SetConfig", {"NewAction": "stop"}, token=token)
|
||||
return {"ok": False, "timeout": True,
|
||||
"error": f"Timeout: Die Taste an der Box wurde nicht innerhalb von {FB_BUTTON_WAIT_SEC:.0f} s "
|
||||
f"gedrueckt — abgebrochen, nichts ausgefuehrt."
|
||||
@@ -1426,7 +1442,12 @@ def _fb_call(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
sf = _fb_second_factor(ip, user, pw)
|
||||
if not sf.get("ok"):
|
||||
return sf
|
||||
r = _tr064(ip, user, pw, svc["type"], svc["control"], action, args)
|
||||
# Gleiche Aktion, gleiche Werte — jetzt mit dem Token im SOAP-Header
|
||||
r = _tr064(ip, user, pw, svc["type"], svc["control"], action, args, token=sf.get("token", ""))
|
||||
if not r.get("ok") and r.get("upnp_error") == "867":
|
||||
return {"ok": False, "error": "Die Box sperrt die Bestaetigung (zu viele Versuche) — bis zu 60 Minuten warten."}
|
||||
if not r.get("ok") and r.get("upnp_error") == "868":
|
||||
return {"ok": False, "error": "Die Box ist mit einer anderen Bestaetigung beschaeftigt — in etwa 2 Minuten nochmal."}
|
||||
if not r.get("ok"):
|
||||
return r
|
||||
# Geheimnisse, die die Box auf Lese-Anfragen herausgibt (WLAN-Schluessel,
|
||||
|
||||
Reference in New Issue
Block a user