Last active
August 8, 2024 00:55
-
-
Save DexterHaslem/d0365dd4cbbcceac22a002fa981beaae to your computer and use it in GitHub Desktop.
CM1000 modem log scraper thing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## this is a quick and dirty script to remotely login to a Netgear CM1000 modem, | |
# grab the docsis logs and format them as a csv for saving later. the webpage will only show so many | |
# and logs can be eventually be lost. this is also an easier way to grab/view them | |
# last tested on Firmware Version V7.01.01 jan 2 2022 | |
import requests | |
import re | |
from time import time | |
from csv import DictWriter | |
from xml.dom.minidom import parseString | |
from datetime import datetime | |
# CONFIG SECTION | |
# i dont beleive this can be changed, or at least every ip leaves it provisioned the same | |
MODEM_ADDR = 'http://192.168.100.1/' | |
LOGIN_USERNAME = 'admin' | |
LOGIN_PASSWORD = 'password' # PROBABLY CHANGE ME | |
now = datetime.now() | |
def murl(endpoint): | |
return MODEM_ADDR + endpoint | |
def build_output_fn(): | |
datestr = '{:02d}_{:02d}_{}'.format(now.month, now.day, now.year) | |
timestr = '{:02d}.{:02d}.{:02d}'.format(now.hour, now.minute, now.second) | |
return 'modem_log_{}-{}.csv'.format(datestr, timestr) | |
webtoken_regex = re.compile(r'<input type=\"hidden\" name=\"webToken\" (?:value=)([0-9]*)') | |
xmlformat_regex = re.compile(r'var xmlFormat = \'([^\']*)\'') | |
login = { | |
'loginUsername': {LOGIN_USERNAME}, | |
'loginPassword': {LOGIN_PASSWORD}, | |
'login': '1', | |
# webtoken will be grabbed from login page | |
} | |
# step 0: get webtoken value.. doesnt seem to be current ts. | |
loginpage = requests.get(murl('GenieLogin.asp')) | |
if loginpage.status_code != 200: | |
exit(1) | |
# # grab this element <input type="hidden" name="webToken" value=1641040522 /> | |
# we need webtoken value for login form | |
for l in loginpage.text.splitlines(): | |
l = l.strip() | |
if 'webToken' in l: | |
# luckily its only present once | |
g = webtoken_regex.match(l) | |
if g: | |
login['webToken'] = g.groups()[0] | |
break | |
if 'webToken' not in login: | |
exit(2) | |
# we need to follow the redirect manually. it slams socket shut after 302 | |
x = requests.post(murl('goform/GenieLogin'), data = login, allow_redirects = False) | |
# now we need to post a plain get to index to get our session id cookie | |
# do this manually, cuz it leaves trailing %00 on the crap | |
# it has just /Index.asp in it | |
sc = requests.get(murl('Index.asp')) | |
if sc.status_code != 200: | |
exit(3) | |
sid = sc.cookies.get('SessionID') | |
x = requests.get(murl('EventLog.asp'), cookies={'SessionID':sid}) | |
# now the worst part. the table entries are in the received js | |
# function InitTagValue() | |
# { | |
# var xmlFormat = '<docsDevEventTable><tr><docsDevEvIndex> .... 1000s | |
# so dig out that xml :/ | |
results = xmlformat_regex.findall(x.text) | |
if len(results) > 0: | |
# note: unsafe against virtually every xml attack | |
doc = parseString(results[0]) | |
# convert to csv and dump for this time ran | |
fn = build_output_fn() | |
with open(fn, 'w', newline='') as out_csv: | |
# TODO: maybe use less dumb names, but we used element names as is | |
d = DictWriter(out_csv, fieldnames=['docsDevEvIndex', 'docsDevEvFirstTime', | |
'docsDevEvLastTime', 'docsDevEvCounts', 'docsDevEvLevel', 'docsDevEvId', 'docsDevEvText']) | |
d.writeheader() | |
for tr in doc.documentElement.childNodes: | |
row = {} | |
for se in tr.childNodes: | |
v = se.childNodes[0].nodeValue | |
# HACK: remove commas in first/last time since we're using csv. if not done it will quote | |
if se.localName == 'docsDevEvFirstTime' or se.localName == 'docsDevEvLastTime': | |
v = v.replace(',', '') | |
row[se.nodeName] = v | |
d.writerow(row) | |
print('**DONE**') |
Dude! You saved me HOURS of work - thank you. I also "forked" a lesser version for the CM600 in Python2.7
Dude! You saved me HOURS of work - thank you. I also "forked" a lesser version for the CM600 in Python2.7
I banged this out kinda quick and dirty as it appears many moons ago and thought "should i throw it in a repo in case anyone else wants to do this? naw a public gist is probably good enough", glad to hear people are still finding it and its useful
Any chance someone could update this to work with a CM3000?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
cool thanks for sharing!