def _parse_uptime_to_seconds(html): regex = "Uptime</b></td>(?:.+?)<td width=60%><font size=2>([0-9]+)day:([0-9]+)h:([0-9]+)m:([0-9]+)s</td>" regex_uptime = re.compile(regex, re.DOTALL) match_object = regex_uptime.search(html) if match_object is None: raise RouterParseError("Cannot _parse uptime") days, hours, minutes, seconds = map(int, match_object.groups()) return days * 86400 + hours * 3600 + minutes * 60 + seconds
def _parse_wireless_settings(html_main, html_basic, html_advanced, html_security): obj = WirelessSettings() obj.add_security_support(WirelessSettings.SECURITY_TYPE_WEP64) obj.add_security_support(WirelessSettings.SECURITY_TYPE_WEP128) obj.add_security_support(WirelessSettings.SECURITY_TYPE_WPA) obj.add_security_support(WirelessSettings.SECURITY_TYPE_WPA2) obj.set_auto_channel_support(False) # the naming of the radio button is weird! is_enabled = '<input type="radio" name="wlanDisabled" value="yes" checked>' in html_main obj.set_enabled_status(is_enabled) match_object = re.compile('document.wlanSetup.ssid.value="(.+?)";').search(html_basic) if match_object is None: raise RouterParseError('Cannot determine SSID') obj.set_ssid(match_object.group(1)) match_object = re.compile('var defaultChan = ([0-9]+)[\s\n]').search(html_basic) if match_object is None: raise RouterParseError('Cannot determine channel') obj.set_channel(int(match_object.group(1))) is_broadcasting_ssid = '<input type="radio" name="hiddenSSID" value="no" checked>' in html_advanced obj.set_ssid_broadcast_status(is_broadcasting_ssid) match_object = re.compile('methodVal = ([0-3]);').search(html_security) if match_object is None: raise RouterParseError('Cannot determine security type') security_type = int(match_object.group(1)) if security_type == 0: obj.set_security_type(obj.__class__.SECURITY_TYPE_NONE) elif security_type == 1: # WEP of some sort match_object = re.compile('var wepTbl =\s+new Array\("([0-9]+)"\);').search(html_security) if match_object is None: raise RouterParseError('Cannot determine WEP bit length') if int(match_object.group(1)) in (0, 1): obj.set_security_type(obj.__class__.SECURITY_TYPE_WEP64) else: obj.set_security_type(obj.__class__.SECURITY_TYPE_WEP128) # WEP passwords cannot be extracted! It shows "***" only elif security_type == 2: # WPA of some sort match_object = re.compile('var wpaCipherTbl =(?:\s+)?new Array\("([0-9]+)"\);').search(html_security) if match_object is None: raise RouterParseError('Cannot determine WPA type') if int(match_object.group(1)) in (0, 1, 3): # 0, 1 = WPA; 3 = mixed obj.set_security_type(obj.__class__.SECURITY_TYPE_WPA) else: # all other values are WPA2 only obj.set_security_type(obj.__class__.SECURITY_TYPE_WPA2) match_object = re.compile('var pskValueTbl = new Array\("(.+?)"\);').search(html_security) if match_object is None: raise RouterParseError('Cannot determine WPA password') obj.set_password(match_object.group(1)) else: # 3 = WPA Radius raise NotImplementedError('Security type not supported') return obj
def _parse_dhcp_settings(html): settings = DHCPServerSettings() settings.set_enabled_status( '<option selected value="2">Server</option>' in html) match_object = re.compile( '<input type="text" name="dhcpRangeStart" size="12" maxlength="15" value="(.+?)">' ).search(html) if match_object is None: raise RouterParseError("Cannot determine DHCP start IP") settings.set_ip_start(match_object.group(1)) match_object = re.compile( '<input type="text" name="dhcpRangeEnd" size="12" maxlength="15" value="(.+?)">' ).search(html) if match_object is None: raise RouterParseError("Cannot determine DHCP end IP") settings.set_ip_end(match_object.group(1)) settings.ensure_valid() return settings
def _parse_dhcp_settings(html): is_enabled = 'var choice= 2 ;' in html regex = re.compile('<input type=text name="dhcpRangeStart" size=16 ' 'maxlength=15 value="(.+?)"' '(?:.+?)' '<input type=text name="dhcpRangeEnd" size=16 ' 'maxlength=15 value="(.+?)"', re.DOTALL) match_object = regex.search(html) if match_object is None: raise RouterParseError('Cannot determine DMZ range!') for ip in match_object.groups(): if not validator.is_valid_ip_address(ip): raise RouterParseError('Invalid DHCP IP: %s' % ip) ip_start, ip_end = match_object.groups() obj = DHCPServerSettings() obj.set_enabled_status(is_enabled) obj.set_ip_start(ip_start) obj.set_ip_end(ip_end) obj.ensure_valid() return obj
def _parse_uptime_to_seconds(string): """Parses an uptime string such as `0 day(s) 10:11:12` or `0 days 10:18:45` to seconds. These 2 strings both appear in the router interface. """ regex = re.compile('([0-9]+) (?:days|day\(s\)) ([0-9]+):([0-9]+):([0-9]+)') match_object = regex.match(string) if match_object is None: raise RouterParseError('Invalid uptime string `%s`' % str(string)) days, hours, minutes, seconds = map(int, match_object.groups()) return days * 86400 + hours * 3600 + minutes * 60 + seconds
def _parse_dhcp_settings(html): regex = re.compile('name="dhcpRange(?:Start|End)" size="15" maxlength="15" value="(.+?)"') try: ip_start, ip_end = regex.findall(html) except ValueError: raise RouterParseError('Cannot find DHCP start/end range') else: settings = DHCPServerSettings() settings.set_ip_start(ip_start) settings.set_ip_end(ip_end) settings.set_enabled_status('<option selected value="2"><script>dw(Enable)</script></option>' in html) settings.ensure_valid() return settings
def _parse_addr_reservation_list(html): regex = '//\s+nvram = \{(.+?)\};\n\nif ' match_object = re.compile(regex, re.DOTALL).search(html) if match_object is None: raise RouterParseError('Cannot parse reservation list') array = _parse_data_structure(match_object.group(1)) try: lst = DHCPReservationList() lst.set_reboot_requirement_status(False) for part in array['dhcpd_static'].split('>'): if part == '': continue mac, ip, name = part.split('<') item = DHCPReservationListItem() item.set_mac(mac) item.set_ip(ip) lst.append(item) except (KeyError, ValueError): raise RouterParseError('Bad nvram for reservation list') return lst
def _parse_connected_clients_list(html): dhcp_list = re.compile('var dhcpList=new Array\((.*)\);').search(html) if dhcp_list is None: raise RouterParseError('Cannot find DHCP list.') dhcp_list = dhcp_list.group(1) results = re.compile("'(.+?);(.+?);(.+?);[01];(\d+)'").findall(dhcp_list) lst = ConnectedClientsList() for client_name, ip, mac, lease_time in results: if not validator.is_valid_ip_address(ip): raise RouterParseError('Invalid IP address: %s' % ip) if not validator.is_valid_mac_address(mac): raise RouterParseError('Invalid MAC address: %s' % mac) item = ConnectedClientsListItem() item.set_client_name(client_name) item.set_mac(converter.normalize_mac(mac)) item.set_ip(ip) item.set_lease_time(int(lease_time)) lst.append(item) return lst
def _parse_pppoe_online_time(status_js): # Ensure that we're using PPPoE nvram = _parse_js_structure(status_js, 'nvram') try: if nvram['wan_proto'] != 'pppoe': return None regex = "stats.wanuptime = '(\d+) days?, (\d+):(\d+):(\d+)';" match_object = re.compile(regex).search(status_js) if match_object is None: return None days, hours, minutes, seconds = map(int, match_object.groups()) return days * 86400 + hours * 3600 + minutes * 60 + seconds except (KeyError, ValueError): raise RouterParseError('Cannot parse pppoe online time!')
def _parse_connected_clients_list(html): regex_dhcp_list = "<tr class=table2 align=center><td><font size=2>(.+?)</td><td><font size=2>(.+?)</td>" lst = ConnectedClientsList() for id, (ip, mac) in enumerate(re.findall(regex_dhcp_list, html), start=1): if ip == "None": # this entry is added when there are no connected clients break if not validator.is_valid_ip_address(ip): raise RouterParseError('Invalid IP address: %s' % ip) if not validator.is_valid_mac_address(mac): raise RouterParseError('Invalid MAC address: %s' % mac) item = ConnectedClientsListItem() item.set_client_name('Client %d' % id) item.set_mac(converter.normalize_mac(mac)) item.set_ip(ip) item.set_lease_time(0) lst.append(item) return lst
def _parse_pppoe_online_time(html): # <TD NOWRAP width="50%">23:05:47</td> # or # <TD NOWRAP width="50%"> 1 day 00:41:36</td> # or # <TD NOWRAP width="50%"> 10 days 00:41:36</td> regex = re.compile('<B>Connection Time</B>(?:.+?)>(?: ([0-9]+) days? )?([0-9]+):([0-9]+):([0-9]+)</td>', re.DOTALL) match_object = regex.search(html) if match_object is None: raise RouterParseError('Cannot parse online time information') days, hours, minutes, seconds = map(lambda x: int(x) if x is not None else 0, match_object.groups()) return days * 86400 + hours * 3600 + minutes * 60 + seconds
def generate_wireless_settings_link(html): """The link with the wireless settings is generated dynamically depending on 2 variables find on every page. These are the ``es0`` and ``cskm`` variables. What ``es0`` controls is not yet known, but ``cskm`` represents the type of wireless security that's setup now. """ regex = re.compile('es0="([0-9]+)";(?:.+?)cskm="(.+?)";', re.DOTALL) match_object = regex.search(html) if match_object is None: raise RouterParseError('Cannot parse wireless link variables!') es0, cskm = match_object.groups() link_value, security_type = _resolve_wireless_security_type(cskm) from time import time return security_type, 'wlan.htm?rc=&rf=%s&_=&_=&_=&_=&_=%s&_=&ZT=%d' % ( link_value, es0, time())
def _parse_addr_reservation_list(html): reservations = re.compile('var StaticList = new Array\((.*)\);').search(html) if reservations is None: raise RouterParseError('Cannot find reservations list.') reservations_list = reservations.group(1) results = re.compile(';(.+?);(.+?);([12]);(\d+)').findall(reservations_list) reservation_list = DHCPReservationList() reservation_list.set_reboot_requirement_status(False) for ip, mac, mac_bind, _time in results: item = DHCPReservationListItem() item.set_mac(converter.normalize_mac(mac)) item.set_ip(ip) item.set_enabled_status(mac_bind == '1') reservation_list.append(item) return reservation_list
def _parse_traffic_stats(html): # The monthly_history array contains one or more other arrays. # Each of those arrays contains 3 items (like a 3-tuple), which are: # 1. time data, for which 2. and 3. are # 2. traffic received in KB # 3. traffic sent in KB regex = 'monthly_history = \[\n(.+?)\];' match_object = re.compile(regex).search(html) if match_object is not None: try: array = ast.literal_eval('[%s]' % match_object.group(1)) kb_recv, kb_sent = 0, 0 for time_data, recv, sent in array: kb_recv += recv kb_sent += sent return TrafficStats(kb_recv * 1024, kb_sent * 1024, 0, 0) except (KeyError, ValueError): raise RouterParseError('Cannot parse traffic stats!') return TrafficStats(0, 0, 0, 0)
def _parse_js_structure(js, name): """Extracts a javascript object by its name. Here's what it looks like initially: // object_name = { some stuff here }; // We're parsing the broken JSON inside, and we're returning a dictionary. """ regex = '//\n%s = \{(.+?)\};\n\n//' % name match_object = re.compile(regex, re.DOTALL).search(js) if match_object is None: raise RouterParseError('Cannot parse main structure data') return _parse_data_structure(match_object.group(1))
def _parse_dmz_settings(html): regex = "//\s+nvram = \{(.+?)\};\n\nvar lipp = '(.+?)';" match_object = re.compile(regex, re.DOTALL).search(html) if match_object is None: raise RouterParseError('Cannot parse DMZ settings') bad_json_settings, lipp = match_object.groups() nvram = _parse_data_structure(bad_json_settings) ip = nvram['dmz_ipaddr'] if '.' not in ip: # it's the last part only.. it's shortened # and the rest is in lipp ip = lipp + ip obj = DMZSettings() obj.set_supported_status(True) obj.set_reboot_requirement_status(False) obj.set_enabled_status(nvram['dmz_enable'] == '1') obj.set_ip(ip) obj.ensure_valid() return obj
def _parse_connected_clients_list(html): lst = ConnectedClientsList() regex = "<tr>(?:.+?)<span class=\"ttext\">(.+?)</span>(?:.+?)<span class=\"ttext\">(.+?)</span>(?:.+?)<span class=\"ttext\">(.+?)</span>(?:.+?)</tr>" for ip, name, mac in re.compile(regex, re.DOTALL).findall(html): if ip == '--': # I've seen such entries on WGR614v7 only.. let's ignore them continue if not validator.is_valid_ip_address(ip): raise RouterParseError('Invalid IP address: %s' % ip) item = ConnectedClientsListItem() item.set_client_name(name) item.set_mac(converter.normalize_mac(mac)) item.set_ip(ip) item.set_lease_time(None) # no lease time information available lst.append(item) return lst
def _parse_connected_clients_list(html): # the last 2 elements of the data array are not needed result = _extract_js_array_data(html, 'DHCPDynList')[:-2] lst = ConnectedClientsList() # each 4 subsequent items are related (client_name, mac_address, ip, lease_time) for client_name, mac, ip, lease_time in split_in_groups(result, 4): if not validator.is_valid_ip_address(ip): raise RouterParseError('Invalid IP address: %s' % ip) item = ConnectedClientsListItem() item.set_client_name(client_name) item.set_mac(converter.normalize_mac(mac)) item.set_ip(ip) if lease_time == 'Permanent': item.set_lease_time(item.__class__.LEASE_TIME_PERMANENT) else: item.set_lease_time(_parse_lease_time(lease_time)) lst.append(item) return lst
def _parse_uptime(status_js): data = _parse_js_structure(status_js, 'sysinfo') try: return int(data['uptime']) except (KeyError, ValueError): raise RouterParseError('Cannot parse uptime!')
def _parse_mac_address(html): match_obj = re.compile('wan_mac="(.+?)";').search(html) if match_obj is None: raise RouterParseError('Cannot determine WAN mac address') return converter.normalize_mac(match_obj.group(1))
def _parse_pppoe_online_time(html): match_obj = re.compile('conntime="(.+?)";').search(html) if match_obj is None: raise RouterParseError('Cannot determine connection time') return int(match_obj.group(1))
def _parse_wireless_settings(html_basic, html_advanced, html_security, html_wep): settings = WirelessSettings() settings.add_security_support(WirelessSettings.SECURITY_TYPE_WEP64) settings.add_security_support(WirelessSettings.SECURITY_TYPE_WEP128) settings.add_security_support(WirelessSettings.SECURITY_TYPE_WPA) settings.add_security_support(WirelessSettings.SECURITY_TYPE_WPA2) match_object = re.compile("var wps_ssid_old='(.+?)';").search(html_basic) if match_object is None: raise RouterParseError("Cannot find SSID!") settings.set_ssid(match_object.group(1)) match_object = re.compile("var wps_disabled=(0|1);").search(html_basic) if match_object is None: raise RouterParseError("Cannot determine wireless enabled status!") settings.set_enabled_status(match_object.group(1) == "0") match_object = re.compile("defaultChan\[wlan_idx\]=(.+?);").search( html_basic) if match_object is None: raise RouterParseError("Cannot determine wireless channel!") settings.set_channel(int(match_object.group(1))) if "name=\"hiddenSSID\" value=\"no\"checked" not in html_advanced: settings.set_ssid_broadcast_status(False) # This is the security type (WEP, WPA, WPA2..) match_object = re.compile("var wps_encrypt_old=([0-4]);").search( html_security) if match_object is None: raise RouterParseError("Cannot determine security type!") security_type = int(match_object.group(1)) if security_type == 1: # WEP match_object = re.compile("var wps_wep_keylen_old='([1-2])';").search( html_wep) if match_object is None: raise RouterParseError("Cannot determine WEP key length!") if int(match_object.group(1)) == 1: # 64bit settings.set_security_type(settings.__class__.SECURITY_TYPE_WEP64) else: # 128bit or something new that we don't handle settings.set_security_type(settings.__class__.SECURITY_TYPE_WEP128) elif security_type == 2: # WPA-PSK settings.set_security_type(settings.__class__.SECURITY_TYPE_WPA) elif security_type == 4: # WPA2-PSK settings.set_security_type(settings.__class__.SECURITY_TYPE_WPA2) else: # Either 0=No security or something else, which we don't handle settings.set_security_type(settings.__class__.SECURITY_TYPE_NONE) if settings.security_type_is_wpa: match_object = re.compile("var wps_psk_old='(.+?)';").search( html_security) if match_object is None: raise RouterParseError('Cannot determine wireless password!') settings.set_password(match_object.group(1)) elif settings.security_type_is_wep: # WEP passwords are rendered as '****' on the page settings.set_password(None) else: # No security or something else settings.set_password("") return settings
def _parse_mac_address(status_js): nvram = _parse_js_structure(status_js, 'nvram') try: return converter.normalize_mac(nvram['wan_hwaddr']) except KeyError: raise RouterParseError('Cannot parse MAC address!')
def _parse_dns_servers(data_array): try: dns_ips = data_array[11].split(' , ') return [ip.strip(' ') for ip in dns_ips if validator.is_valid_ip_address(ip)] except IndexError, e: raise RouterParseError('Cannot access the array index: %s' % repr(e))
def _parse_mac_address(data_array): try: return converter.normalize_mac(data_array[1]) except IndexError, e: raise RouterParseError('Cannot access the array index: %s' % repr(e))
def _parse_uptime(data_array): try: return int(data_array[4]) except IndexError, e: raise RouterParseError('Cannot access the array index: %s' % repr(e))
def _parse_http_id(html): regex = "src='status-data.jsx\?_http_id=(TID(?:.+?))'>" match_object = re.compile(regex).search(html) if match_object is None: raise RouterParseError('Cannot determine http id!') return match_object.group(1)
def _parse_uptime(html): match_obj = re.compile('uptime=\s"(.+?)";').search(html) if match_obj is None: raise RouterParseError('Cannot determine uptime') return int(match_obj.group(1))
def _parse_wireless_settings(security_type, html_basic, html_advanced): settings = WirelessSettings() settings.add_security_support(WirelessSettings.SECURITY_TYPE_WEP64) settings.add_security_support(WirelessSettings.SECURITY_TYPE_WEP128) settings.add_security_support(WirelessSettings.SECURITY_TYPE_WPA) settings.add_security_support(WirelessSettings.SECURITY_TYPE_WPA2) settings.set_ascii_wep_password_support_status(False) settings.set_reboot_requirement_status(False) # Determine the submit token.. some WGR614v9 models have such a token # It's either some form of CSRF protection or something else.. match_object = re.compile( '<form method="POST" action="wireless.cgi\?id=([0-9]+)">').search( html_basic) if match_object is None: settings.set_internal_param('submit_token', None) else: settings.set_internal_param('submit_token', int(match_object.group(1))) if security_type == 'WEP': if '<option selected value="1">64bit</option>' in html_basic: settings.set_security_type(WirelessSettings.SECURITY_TYPE_WEP64) elif '<option selected value="2">128bit</option>' in html_basic: settings.set_security_type(WirelessSettings.SECURITY_TYPE_WEP128) else: raise RouterParseError('Cannot determine WEP key length') settings.set_password(__parse_wep_password(html_basic)) elif security_type == 'WPA-PSK': settings.set_security_type(WirelessSettings.SECURITY_TYPE_WPA) # password extraction is done below elif security_type in ('WPA2-PSK', 'WPA-AUTO-PSK'): settings.set_security_type(WirelessSettings.SECURITY_TYPE_WPA2) # password extraction is done below else: # security_type = `Disable` or something else that we don't handle.. settings.set_security_type(WirelessSettings.SECURITY_TYPE_NONE) settings.set_password('') if settings.security_type_is_wpa: regex = re.compile( '<input type="text" name="passphrase" size=20 maxLength=64 value="(.+?)" onFocus' ) match_object = regex.search(html_basic) if match_object is None: raise RouterParseError('Cannot determine WPA password') password = match_object.group(1) if '*****' in password: # WGR614v7 doesn't present us with the real password, but substitutes it with * chars # that's not the case for v8 and v9 though password = None settings.set_password(password) regex = re.compile('<input type="text" name="ssid" value="(.+?)"') match_object = regex.search(html_basic) if match_object is None: raise RouterParseError('Cannot determine SSID') settings.set_ssid(match_object.group(1)) regex = re.compile( '<input type="hidden" name="initChannel" value="([0-9]+)">') match_object = regex.search(html_basic) if match_object is None: raise RouterParseError('Cannot determine channel') settings.set_channel(int(match_object.group(1))) if '<input type="checkbox" checked name="enable_ap" value="enable_ap">' in html_advanced: is_enabled = True else: is_enabled = False settings.set_enabled_status(is_enabled) if '<input type="checkbox" checked name="ssid_bc" value="ssid_bc">' in html_advanced: is_broadcasting = True else: is_broadcasting = False settings.set_ssid_broadcast_status(is_broadcasting) if '<input type="checkbox" checked name="enable_wmm" value="enable_wmm">' in html_advanced: is_wmm_enabled = True else: is_wmm_enabled = False settings.set_internal_param('enable_wmm', is_wmm_enabled) regex = re.compile( "Select Region(?:.+?)<option selected value=\"([0-9]+)\">") match_object = regex.search(html_basic) if match_object is None: raise RouterParseError('Cannot determine Region value') settings.set_internal_param('WRegion', int(match_object.group(1))) return settings
def _parse_traffic_stats(data_array): data_array = data_array[:4] if len(data_array) != 4: raise RouterParseError('Unexpected stats size: %d' % len(data_array)) data_array = map(lambda x: int(x.replace(',', '')), data_array) return TrafficStats(*data_array)