98 lines
3.4 KiB
Python
Executable File
98 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Back up one MTD partition of the GPON stick over telnet, md5-verified.
|
|
Usage: gpon_backup.py <mtd_index> <outfile> [idle_timeout] [hard_cap]
|
|
Streams `cat /dev/mtdN | uuencode` over the telnet session and uudecodes locally
|
|
(Python's uu module was removed in 3.13, so we decode by hand)."""
|
|
import socket, sys, time, re, hashlib
|
|
from gpon_env import HOST, PORT, USER, PASS
|
|
|
|
idx = sys.argv[1]
|
|
outfile = sys.argv[2]
|
|
idle_to = float(sys.argv[3]) if len(sys.argv)>3 else 12.0
|
|
hard_cap = float(sys.argv[4]) if len(sys.argv)>4 else 300.0
|
|
|
|
def strip_iac(data, outbuf):
|
|
res=bytearray(); i=0
|
|
while i<len(data):
|
|
b=data[i]
|
|
if b==0xFF and i+1<len(data):
|
|
c=data[i+1]
|
|
if c in (251,252,253,254) and i+2<len(data):
|
|
o=data[i+2]
|
|
if c==253: outbuf.append(bytes([255,252,o]))
|
|
elif c==251: outbuf.append(bytes([255,254,o]))
|
|
i+=3; continue
|
|
elif c==250:
|
|
j=data.find(b'\xff\xf0', i+2); i=len(data) if j<0 else j+2; continue
|
|
else: i+=2; continue
|
|
res.append(b); i+=1
|
|
return bytes(res)
|
|
|
|
s=socket.create_connection((HOST,PORT),timeout=8)
|
|
def rx(idle, cap, stop=None):
|
|
s.settimeout(0.5); buf=bytearray(); last=time.time(); start=time.time()
|
|
while True:
|
|
try:
|
|
d=s.recv(16384)
|
|
if not d: break
|
|
ob=[]; buf.extend(strip_iac(d,ob))
|
|
for o in ob: s.sendall(o)
|
|
last=time.time()
|
|
if stop and stop(buf): break
|
|
except socket.timeout:
|
|
if time.time()-last>idle: break
|
|
if time.time()-start>cap: break
|
|
return buf.decode('latin-1','replace')
|
|
|
|
def send(x): s.sendall(x.encode()+b'\r\n')
|
|
|
|
out=rx(2,8)
|
|
if re.search(r'(ogin|sername)',out): send(USER); out+=rx(2,6)
|
|
if re.search(r'assword',out,re.I): send(PASS); out+=rx(2,6)
|
|
send('system'); rx(1.5,5)
|
|
send('shell'); rx(1.5,5)
|
|
|
|
send('md5sum /dev/mtd%s'%idx)
|
|
m=rx(90,150, stop=lambda b: re.search(rb'[0-9a-f]{32}\s+/dev/mtd',b))
|
|
mm=re.search(r'([0-9a-f]{32})\s+/dev/mtd%s'%re.escape(idx), m)
|
|
expected=mm.group(1) if mm else None
|
|
|
|
t0=time.time()
|
|
send('cat /dev/mtd%s | uuencode p'%idx)
|
|
def done(b):
|
|
bi=b.find(b'begin ')
|
|
return bi>=0 and (b'\nend\r' in b[bi:] or b'\nend\n' in b[bi:])
|
|
d=rx(idle_to, hard_cap, stop=done)
|
|
dt=time.time()-t0
|
|
try: s.close()
|
|
except Exception: pass
|
|
|
|
def uudecode(text):
|
|
out=bytearray(); started=False
|
|
for ln in text.split('\n'):
|
|
ln=ln.rstrip('\r')
|
|
if not started:
|
|
if ln.startswith('begin '): started=True
|
|
continue
|
|
if ln=='end': break
|
|
if ln=='' or ln=='`': continue
|
|
n=(ord(ln[0])-0x20)&0x3f
|
|
if n<=0: continue
|
|
data=ln[1:]; dec=bytearray()
|
|
for i in range(0,len(data),4):
|
|
g=data[i:i+4]; v=[(ord(c)-0x20)&0x3f for c in g]
|
|
while len(v)<4: v.append(0)
|
|
dec.append(((v[0]<<2)|(v[1]>>4))&0xff)
|
|
dec.append(((v[1]<<4)|(v[2]>>2))&0xff)
|
|
dec.append(((v[2]<<6)|v[3])&0xff)
|
|
out.extend(dec[:n])
|
|
return bytes(out)
|
|
|
|
raw=uudecode(d)
|
|
with open(outfile,'wb') as f: f.write(raw)
|
|
actual=hashlib.md5(raw).hexdigest()
|
|
print("mtd%s -> %s : %d bytes in %.1fs"%(idx,outfile,len(raw),dt))
|
|
print(" expected md5 (stick): %s"%expected)
|
|
print(" actual md5 (local): %s"%actual)
|
|
print(" VERIFY: %s"%("OK" if (expected and actual==expected) else "*** MISMATCH / INCOMPLETE ***"))
|