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').
|
# schlanker Reader. Braucht FritzBox-Login (Credential-Store, Typ 'fritzbox').
|
||||||
|
|
||||||
def _tr064(ip: str, user: str, pw: str, service: str, control: str,
|
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."""
|
"""Ein TR-064-SOAP-Call. Gibt {ok, fields|error}. fields = alle <NewX>-Tags."""
|
||||||
import requests
|
import requests
|
||||||
from requests.auth import HTTPDigestAuth
|
from requests.auth import HTTPDigestAuth
|
||||||
@@ -1221,7 +1221,14 @@ def _tr064(ip: str, user: str, pw: str, service: str, control: str,
|
|||||||
envelope = (
|
envelope = (
|
||||||
'<?xml version="1.0"?>'
|
'<?xml version="1.0"?>'
|
||||||
'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" '
|
'<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}>'
|
f'<u:{action} xmlns:u="{service}">{body}</u:{action}>'
|
||||||
'</s:Body></s:Envelope>'
|
'</s:Body></s:Envelope>'
|
||||||
)
|
)
|
||||||
@@ -1374,18 +1381,27 @@ def _fb_second_factor(ip: str, user: str, pw: str) -> dict:
|
|||||||
if not st.get("ok"):
|
if not st.get("ok"):
|
||||||
return {"ok": False, "error": f"Bestaetigung nicht startbar: {st.get('error')}"}
|
return {"ok": False, "error": f"Bestaetigung nicht startbar: {st.get('error')}"}
|
||||||
methods = st["fields"].get("NewMethods", "")
|
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)
|
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
|
deadline = time.time() + FB_BUTTON_WAIT_SEC
|
||||||
state = st["fields"].get("NewState", "")
|
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:
|
while time.time() < deadline:
|
||||||
if state == "authenticated":
|
if state == "authenticated":
|
||||||
return {"ok": True}
|
logger.info("[fritzbox] %s: bestaetigt", ip)
|
||||||
if state in ("blocked", "failure", "stopped"):
|
return {"ok": True, "token": token}
|
||||||
return {"ok": False, "error": f"Bestaetigung abgebrochen (Box meldet '{state}')."}
|
if state in reasons and state != "disabled":
|
||||||
|
return {"ok": False, "error": f"Bestaetigung nicht erfolgt — Box meldet '{state}': {reasons[state]}."}
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
g = _tr064(ip, user, pw, auth["type"], auth["control"], "GetState")
|
g = _tr064(ip, user, pw, auth["type"], auth["control"], "GetState", token=token)
|
||||||
state = g.get("fields", {}).get("NewState", state) if g.get("ok") else state
|
new = g.get("fields", {}).get("NewState", state) if g.get("ok") else state
|
||||||
_tr064(ip, user, pw, auth["type"], auth["control"], "SetConfig", {"NewAction": "stop"})
|
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,
|
return {"ok": False, "timeout": True,
|
||||||
"error": f"Timeout: Die Taste an der Box wurde nicht innerhalb von {FB_BUTTON_WAIT_SEC:.0f} s "
|
"error": f"Timeout: Die Taste an der Box wurde nicht innerhalb von {FB_BUTTON_WAIT_SEC:.0f} s "
|
||||||
f"gedrueckt — abgebrochen, nichts ausgefuehrt."
|
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)
|
sf = _fb_second_factor(ip, user, pw)
|
||||||
if not sf.get("ok"):
|
if not sf.get("ok"):
|
||||||
return sf
|
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"):
|
if not r.get("ok"):
|
||||||
return r
|
return r
|
||||||
# Geheimnisse, die die Box auf Lese-Anfragen herausgibt (WLAN-Schluessel,
|
# Geheimnisse, die die Box auf Lese-Anfragen herausgibt (WLAN-Schluessel,
|
||||||
|
|||||||
Reference in New Issue
Block a user