├── wakeup.json ├── README.md └── wakeup.py /wakeup.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "your.hostname.or.ip", 3 | "port": 443, 4 | "username": "your_username", 5 | "password": "your_password", 6 | "devices": { 7 | "default": "00:C0:1D:C0:FF:EE", 8 | "foo": "00:00:DE:AD:BE:EF", 9 | "bar": "00:01:02:03:04:05" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Some scripts for easy Fritzbox handling 2 | 3 | Currently only Fritzboxes with FRITZ!OS v6.50 or newer are supported. 4 | 5 | ### Wake on LAN 6 | 7 | * **wakeup.py** – Python script for remotely starting a computer behind a Fritzbox using its WOL functionality 8 | 9 | #### Required Python modules 10 | 11 | * lxml 12 | * packaging 13 | * requests 14 | 15 | #### Configuration 16 | 17 | All configuration is done in the config file in JSON format: 18 | 19 | * *host*: external fritzbox hostname or ip 20 | * *port*: ssl port 21 | * *username*/*password*: fritzbox login credentials 22 | * *devices*: list of device names with macs that can be used for wakeup 23 | 24 | If the password line is not present in the config file, you will be prompted for the password. 25 | 26 | #### Usage 27 | 28 | ```sh 29 | > wakeup.py # sends wakeup to 'default' device 30 | > wakeup.py foo # sends wakeup to device 'foo' in config file 31 | > wakeup.py -k foo # sends wakeup to device 'foo' while ignoring ssl certificate verification 32 | > wakeup.py -c yourconfig.json bar # sends wakup to device 'bar' using config file 'yourconfig.json' 33 | > wakeup.py -h # shows help 34 | ``` 35 | -------------------------------------------------------------------------------- /wakeup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # (c)2017-2021 n0rc 5 | 6 | import argparse 7 | import getpass 8 | import hashlib 9 | import json 10 | import os 11 | import requests 12 | import sys 13 | 14 | from lxml import etree 15 | from packaging import version 16 | from requests.exceptions import SSLError 17 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 18 | 19 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 20 | 21 | SID_NOAUTH = '0000000000000000' 22 | 23 | 24 | def error_exit(msg): 25 | print("[error] {}".format(msg)) 26 | sys.exit(1) 27 | 28 | 29 | def ssl_error_exit(): 30 | error_exit("ssl certificate verification failed") 31 | 32 | 33 | def get_config(config_file): 34 | if os.path.isfile(config_file): 35 | with open(config_file, 'r') as jf: 36 | config = json.load(jf) 37 | config['url_login'] = 'https://{}:{}/login_sid.lua'.format(config['host'], config['port']) 38 | config['url_data'] = 'https://{}:{}/data.lua'.format(config['host'], config['port']) 39 | return config 40 | else: 41 | error_exit("config file not found") 42 | 43 | 44 | def get_sid(config): 45 | try: 46 | password = config['password'] 47 | except: 48 | password = getpass.getpass() 49 | try: 50 | r = requests.get(config['url_login'], verify=config['verify_ssl']) 51 | t = etree.XML(r.content) 52 | challenge = t.xpath('//Challenge/text()')[0] 53 | response = '{}-{}'.format(challenge, hashlib.md5('{}-{}'.format(challenge, password).encode('utf-16-le')).hexdigest()) 54 | r = requests.get('{}?username={}&response={}'.format(config['url_login'], config['username'], response), verify=config['verify_ssl']) 55 | t = etree.XML(r.content) 56 | return t.xpath('//SID/text()')[0] 57 | except SSLError: 58 | ssl_error_exit() 59 | 60 | 61 | def get_uid(config, sid, mac): 62 | try: 63 | payload = {'sid': sid, 'page': 'netDev', 'xhrId': 'all'} 64 | r = requests.post(config['url_data'], data=payload, verify=config['verify_ssl']) 65 | devs = json.loads(r.content) 66 | for dev in devs['data']['passive']: 67 | if dev['mac'] == mac: 68 | return dev['UID'] 69 | for dev in devs['data']['active']: 70 | if dev['mac'] == mac: 71 | return dev['UID'] 72 | return '' 73 | except SSLError: 74 | ssl_error_exit() 75 | 76 | 77 | def get_version(config, sid): 78 | try: 79 | payload = {'sid': sid, 'page': 'overview'} 80 | r = requests.post(config['url_data'], data=payload, verify=config['verify_ssl']) 81 | reply = json.loads(r.content) 82 | return reply['data']['fritzos']['nspver'].split().pop(0) 83 | except SSLError: 84 | ssl_error_exit() 85 | except KeyError: 86 | return '0.0' 87 | 88 | 89 | def wake_up(config, sid, uid): 90 | try: 91 | payload = {'sid': sid, 'dev': uid, 'oldpage': 'net/edit_device.lua', 'page': 'edit_device', 'btn_wake': ''} 92 | vers = get_version(config, sid) 93 | if version.parse(vers) <= version.parse('7.24'): 94 | payload['page'] += '2' 95 | r = requests.post(config['url_data'], data=payload, verify=config['verify_ssl']) 96 | if r.headers.get("content-type").startswith('application/json'): 97 | reply = json.loads(r.content) 98 | try: 99 | if reply['data']['btn_wake'] == 'ok': 100 | return True 101 | except KeyError: 102 | pass 103 | return False 104 | elif '"pid":"netDev"' in r.text: 105 | return True 106 | else: 107 | return False 108 | except SSLError: 109 | ssl_error_exit() 110 | 111 | 112 | if __name__ == '__main__': 113 | parser = argparse.ArgumentParser() 114 | parser.add_argument('device', help='device name (from config file) to sent wakeup to', default='default', nargs='?') 115 | parser.add_argument('--config', '-c', default='wakeup.json', metavar='wakeup.json', help='use specified config file') 116 | parser.add_argument('--ssl-no-verify', '-k', action='store_true', help='ignore ssl certificate verification') 117 | args, _ = parser.parse_known_args() 118 | 119 | config = get_config(args.config) 120 | config['verify_ssl'] = not args.ssl_no_verify 121 | 122 | if args.device in config['devices']: 123 | target_mac = config['devices'][args.device] 124 | else: 125 | error_exit("unknown device {}".format(args.device)) 126 | 127 | sid = get_sid(config) 128 | if sid == SID_NOAUTH: 129 | error_exit("authentication failed") 130 | else: 131 | uid = get_uid(config, sid, target_mac) 132 | if uid: 133 | if wake_up(config, sid, uid): 134 | print("[success] wakeup sent to {}".format(target_mac)) 135 | else: 136 | error_exit("something went wrong while sending wakeup to {}".format(target_mac)) 137 | else: 138 | error_exit("unknown mac {}".format(target_mac)) 139 | --------------------------------------------------------------------------------