Skip to content

Commit

Permalink
1.1.4 corr
Browse files Browse the repository at this point in the history
  • Loading branch information
Karsten Thiele committed Dec 21, 2020
1 parent af23652 commit 9d3858f
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 4 deletions.
Binary file added docs/de/userlogin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/de/userpermissions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions lib/fritz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import xml.etree.ElementTree as ET
import urllib.parse
import urllib.request
import time
import hashlib
import sys

"""
Example code Python3
#!/usr/bin/env python3
# vim: expandtab sw=4 ts=4
FRITZ!OS WebGUI Login
Get a sid (session ID) via PBKDF2 based challenge response algorithm. Fallback to MD5 if FRITZ!OS has no PBKDF2 support.
AVM 2020-09-25
"""

LOGIN_SID_ROUTE = "/login_sid.lua?version=2"


class LoginState:
def __init__(self, challenge: str, blocktime: int):
self.challenge = challenge
self.blocktime = blocktime
self.is_pbkdf2 = challenge.startswith("2$")


def get_sid(box_url: str, username: str, password: str) -> str:
""" Get a sid by solving the PBKDF2 (or MD5) challenge-response process. """
try:
state = get_login_state(box_url)
except Exception as ex:
raise Exception("failed to get challenge") from ex
if state.is_pbkdf2:
print("PBKDF2 supported")
challenge_response = calculate_pbkdf2_response(
state.challenge, password)
else:
print("Falling back to MD5")
challenge_response = calculate_md5_response(state.challenge, password)
if state.blocktime > 0:
print(f"Waiting for {state.blocktime} seconds...")
time.sleep(state.blocktime)
try:
sid = send_response(box_url, username, challenge_response)
except Exception as ex:
raise Exception("failed to login") from ex
if sid == "0000000000000000":
raise Exception("wrong username or password")
return sid


def get_login_state(box_url: str) -> LoginState:
""" Get login state from FRITZ!Box using login_sid.lua?version=2 """
url = box_url + LOGIN_SID_ROUTE
http_response = urllib.request.urlopen(url)
xml = ET.fromstring(http_response.read())
# print(f"xml: {xml}")
challenge = xml.find("Challenge").text
blocktime = int(xml.find("BlockTime").text)
return LoginState(challenge, blocktime)


def calculate_pbkdf2_response(challenge: str, password: str) -> str:
""" Calculate the response for a given challenge via PBKDF2 """
challenge_parts = challenge.split("$")
# Extract all necessary values encoded into the challenge
iter1 = int(challenge_parts[1])
salt1 = bytes.fromhex(challenge_parts[2])
iter2 = int(challenge_parts[3])
salt2 = bytes.fromhex(challenge_parts[4])
# Hash twice, once with static salt...
# Once with dynamic salt.
hash1 = hashlib.pbkdf2_hmac("sha256", password.encode(), salt1, iter1)
hash2 = hashlib.pbkdf2_hmac("sha256", hash1, salt2, iter2)
return f"{challenge_parts[4]}${hash2.hex()}"


def calculate_md5_response(challenge: str, password: str) -> str:
""" Calculate the response for a challenge using legacy MD5 """
response = challenge + "-" + password
# the legacy response needs utf_16_le encoding
response = response.encode("utf_16_le")
md5_sum = hashlib.md5()
md5_sum.update(response)
response = challenge + "-" + md5_sum.hexdigest()
return response


def send_response(box_url: str, username: str, challenge_response: str) -> str:
""" Send the response and return the parsed sid. raises an Exception on error """
# Build response params
post_data_dict = {"username": username, "response": challenge_response}
post_data = urllib.parse.urlencode(post_data_dict).encode()
headers = {"Content-Type": "application/x-www-form-urlencoded"}
url = box_url + LOGIN_SID_ROUTE
# Send response
http_request = urllib.request.Request(url, post_data, headers)
http_response = urllib.request.urlopen(http_request)
# Parse SID from resulting XML.
xml = ET.fromstring(http_response.read())
return xml.find("SID").text


def main():
if len(sys.argv) < 4:
print(
f"Usage: {sys.argv[0]} http://fritz.box user pass"
)
exit(1)
url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
sid = get_sid(url, username, password)
print(f"Successful login for user: {username}")
print(f"sid: {sid}")


if __name__ == "__main__":
main()
7 changes: 5 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ var fritzTimeout;
512 = ON_OFF
513 = LEVEL_CTRL
514 = COLOR_CTRL
516 = ? detected with blinds
517 = ? detected with blinds
516 = ? detected with blinds, different alert?
517 = ? detected with blinds, different alerttimestamp
772 = SIMPLE_BUTTON
1024 = SUOTA-Update
*/
Expand Down Expand Up @@ -1734,6 +1734,7 @@ function main() {
},
native: {}
});
adapter.setState(typ + newId + '.blindsopen', { val: false, ack: true }); //Button auf Ausgangszustand
adapter.setObjectNotExists(typ + newId + '.blindsclose', {
type: 'state',
common: {
Expand All @@ -1746,6 +1747,7 @@ function main() {
},
native: {}
});
adapter.setState(typ + newId + '.blindsclose', { val: false, ack: true }); //Button auf Ausgangszustand
adapter.setObjectNotExists(typ + newId + '.blindsstop', {
type: 'state',
common: {
Expand All @@ -1758,6 +1760,7 @@ function main() {
},
native: {}
});
adapter.setState(typ + newId + '.blindsstop', { val: false, ack: true }); //Button auf Ausgangszustand
}

function createDevices() {
Expand Down
4 changes: 2 additions & 2 deletions test/lib/fritzserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function handleHttpRequest(request, response) {
'</Challenge><BlockTime>0</BlockTime><Rights></Rights></SessionInfo>'
);
response.end();
} else if (request.url == '/login_sid.lua?version=2') {
} else if (request.url == '/login_sid.lua?version=2' && request.method == 'GET') {
//check the URL of the current request
response.writeHead(200, { 'Content-Type': 'application/xml' });
response.write(
Expand Down Expand Up @@ -85,7 +85,7 @@ function handleHttpRequest(request, response) {
'</Challenge><BlockTime>0</BlockTime><Rights><Name>Dial</Name><Access>2</Access><Name>App</Name><Access>2</Access><Name>HomeAuto</Name><Access>2</Access><Name>BoxAdmin</Name><Access>2</Access><Name>Phone</Name><Access>2</Access><Name>NAS</Name><Access>2</Access></Rights></SessionInfo>'
);
response.end();
} else if (request.url == '/login_sid.lua?version=2?username=admin&response=' + challengeResponse) {
} else if (request.url == '/login_sid.lua?version=2' && request.method == 'POST') {
//check the URL of the current request
response.writeHead(200, { 'Content-Type': 'application/xml' });
response.write(
Expand Down

0 comments on commit 9d3858f

Please sign in to comment.