#!/usr/bin/env python # -*- coding: utf-8 -*- # filename: n300_dgn2200_reconnect.py # if platform is Windows the script beeps ascending upon changed IP after # reconnect and descending if IP stayed the same. import urllib.request import re import time import platform import argparse from itertools import cycle import string import random import os.path import hashlib class ReconnectError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) def key_gen(size=6, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for x in range(size)) def xor_crypt(s, key): return ''.join(chr(ord(x) ^ord(y)) for (x, y) in zip(s, cycle(key))) def create_xor_data(s): data = s.encode('utf-8') key_raw = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(8)) key = key_raw.encode('ascii') key_hash = hashlib.sha512(key).digest() ba = bytearray(64) ba[:len(data+key)] = data + key for i in range(64): if i <= len(data+key): ba[i] = ba[i] ^ key_hash[i] else: ba[i] = random.randint(0,255) ^ key_hash[i] return (key_raw, bytes(ba)) def decrypt_xor_data(b, password): result = None current = -1 key = password.encode('ascii') key_hash = hashlib.sha512(key).digest() ba = bytearray(b) for i in range(64): ba[i] = current = ba[i] ^ key_hash[i] if current == 0 and ba[i-len(key):i] == key: result = ba[:i-len(key)].decode('utf-8') break return result # a function to contain the reconnect procedure, separate functions for # connect, disconnect would be a nice idea, but I need only this def reconnect(login_password, router_address, login_name, auth_realm, reconnect, store_pass): # pattern to find id id_pattern = re.compile(r'id=([^"]+)"') # pattern to find ip ip_pattern = re.compile(r'IP Address</b></td>[^<]*<td nowrap>' + '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})') key_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.path.splitext(__file__)[0] + '.key') # if password should be stored if store_pass: key, content = create_xor_data(login_password) try: with open(key_file, 'wb') as f: f.write(content) except IOError: print('The password could not be stored. An IO-error occured.') else: print('You can use "{key:s}" as new password instead to ' \ 'access your stored password.'.format(key=key)) # only if password should not bestored, try to receive store password elif os.path.exists(key_file): key = None try: with open(key_file, 'rb') as f: key = decrypt_xor_data(f.read(), login_password) except IOError: print('The password could not be read. An IO-error occured.') else: if key: login_password = key print('Using stored password.') else: print('Stored password could not be decrypted. you may want ' \ 'to delete the .key file and store again if you ' \ 'forgot.') # we need a password-manager, the admin pages are protected with # http basic auth password_manager = urllib.request.HTTPPasswordMgr() password_manager.add_password(auth_realm, router_address, login_name, login_password) # construct opener and install it http_handler = urllib.request.HTTPBasicAuthHandler(password_manager) page_opener = urllib.request.build_opener(http_handler) urllib.request.install_opener(page_opener) # no data for first GET (the connection status page) params = None # if anything error-like happens -> end the script try: # get the connection status page, extract the ID resp = urllib.request.urlopen( '{router_address:s}/setup.cgi?next_file=RST_st_poe.htm'.format( router_address=router_address), params) page = resp.read().decode('utf-8') # getting the old ip match = ip_pattern.search(page) if match: old_ip = match.group(1) else: raise ReconnectError('IP not found.') # if --reconnect is not set only the IP is displayed, reconnection # steps are skipped if not reconnect: print(old_ip) else: # getting id for disconnect match = id_pattern.search(page) if match: id = match.group(1) else: raise ReconnectError('ID not found.') # now execute a disconnect with the ID from last request params = urllib.parse.urlencode({ 'todo' : 'disconnect', 'this_file' : 'RST_st_poe.htm', 'next_file' : 'RST_st_poe.htm', 'SID' : '' }) resp = urllib.request.urlopen( '{router_address:s}/setup.cgi?id={id:s}'.format( router_address=router_address, id=id), params.encode('utf-8')) page = resp.read().decode('utf-8') # every GET/POST will change the ID so we have to extract it again # from the repsonse to the former POST match = id_pattern.search(page) if match: id = match.group(1) else: raise ReconnectError('ID not found.') # now execute a connect with the ID from last request params = urllib.parse.urlencode({ 'todo' : 'connect', 'this_file' : 'RST_st_poe.htm', 'next_file' : 'RST_st_poe.htm', 'SID' : '' }) # the disconnect is not immediately, better wait a little bit time.sleep(2) resp = urllib.request.urlopen( '{router_address:s}/setup.cgi?id={id:s}'.format( router_address=router_address, id=id), params.encode('utf-8')) page = resp.read().decode('utf-8') match = ip_pattern.search(page) if match: new_ip = match.group(1) if platform.system() == 'Windows': import winsound if old_ip == new_ip: winsound.Beep(2000,200) winsound.Beep(1500,200) winsound.Beep(1000,200) else: winsound.Beep(1000,200) winsound.Beep(1500,200) winsound.Beep(2000,200) print(new_ip) else: raise ReconnectError('IP not found.') except urllib.error.HTTPError as err: print('HTTP error ({code:d}) occured.'.format(code=err.code)) if err.code == 401: print('Password or login name may be incorrect.') except ReconnectError as err: print('An error ({error:s}) occured.'.format(error=err)) # argument parsing parser = argparse.ArgumentParser() parser.add_argument('--store-password', dest='store_pass', action='store_const', const=True, help='stores password in file "{fname:s}" and ' \ 'generates access password to use instead for ' \ 'future use.'.format(fname=__file__ + '.key'), default=False ) parser.add_argument('--reconnect', dest='reconnect', action='store_const', const=True, help='reconnects the adsl-modem', default=False ) parser.add_argument('--gui-address', metavar='HOST', dest='router_address', help='the address of GUI with http-prefix (default: \ http://192.168.0.1)', default='http://192.168.0.1' ) parser.add_argument('--login', metavar='LOGIN', help='the login name (default: admin)', default='admin', dest='login_name') parser.add_argument('--password', metavar='PASSWORD', dest='login_password', help='the password for login', required=True) parser.add_argument('--realm', metavar='REALM', help='the login realm (default: NETGEAR DGN2200Bv3)', default='NETGEAR DGN2200Bv3', dest='auth_realm') args = parser.parse_args() reconnect(router_address=args.router_address, auth_realm=args.auth_realm, login_name=args.login_name, login_password=args.login_password, reconnect=args.reconnect, store_pass=args.store_pass)