diff --git a/satellite/satellite.py b/satellite/satellite.py index f4d5c80..69e4ec5 100644 --- a/satellite/satellite.py +++ b/satellite/satellite.py @@ -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 -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 = ( '' '' + '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'{token}' + if token else '') + + '' f'{body}' '' ) @@ -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,