Last active
June 20, 2025 11:42
-
-
Save psa-jforestier/1f8e99b6be687c26d5e6473633ba62ab to your computer and use it in GitHub Desktop.
Script Python pour recupérer des info de la Box SFR 7
This file contains hidden or 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
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
''' | |
Inspired by https://lafibre.info/sfr-les-news/nb6vac-et-api/ | |
Compatible with SFR BOX FIBRE 7 (Router name : GR140CG) | |
Usage : python3 rbox_status.ph -u admin -p password | |
''' | |
import hashlib | |
import hmac | |
import requests | |
import re | |
import time | |
import datetime | |
import argparse | |
import pprint | |
import json | |
import sys | |
import os | |
#import xml.etree.ElementTree as ET | |
def DEBUG(text: str): | |
if (VERBOSE_LEVEL==True): | |
print(text) | |
def _compute_hash(token: str, value: str) -> str: | |
"""Compute single value hash.""" | |
hash = hashlib.sha256(value.encode()).hexdigest() | |
return hmac.new(token.encode(), hash.encode(), hashlib.sha256).hexdigest() | |
def compute_hash(token: str, username: str, password: str) -> str: | |
# the original code will not work for the web interface. | |
# For the API, it is hash(u)+hash(p), | |
# but the web interface use a hash(hash(u)+hash(p)) | |
"""Compute full username/password hash.""" | |
hmac_username = _compute_hash(token, username) | |
hmac_password = _compute_hash(token, password) | |
return f"{hmac_username}{hmac_password}" | |
def Box_GetJSON(session, url): | |
r = session.get(url) | |
j = json.loads(r.text) | |
return j | |
SECTION_INC=0 | |
def Box_PrintBeginSection(fmt, name): | |
global SECTION_INC | |
global output | |
f = sys.stdout if output == None else open(output, "a") | |
if (fmt == 'txt'): | |
print(">" * (3*SECTION_INC) + ">>> " + name, file=f) | |
SECTION_INC = SECTION_INC + 1 | |
def Box_PrintEndSection(fmt, name): | |
global SECTION_INC | |
global output | |
f = sys.stdout if output == None else open(output, "a") | |
if (fmt == 'txt'): | |
print("", file=f) | |
SECTION_INC = SECTION_INC - 1 | |
def Box_PrintInfo(fmt, info, value): | |
global output | |
f = sys.stdout if output == None else open(output, "a") | |
if (fmt == 'txt'): | |
print(info+": "+value.strip(), file=f) | |
return | |
def parse_table(s): | |
# Use regex to find all rows in the string | |
rows = re.findall(r'<tr[^>]*>(.*?)</tr>', s, re.DOTALL) | |
result = [] | |
for row in rows: | |
# Find all cells in the row | |
cells = re.findall(r'<td[^>]*>(.*?)</td>', row, re.DOTALL) | |
# Clean up the cell data by stripping whitespace and HTML tags | |
cleaned_cells = [re.sub(r'<.*?>', '', cell).strip() for cell in cells] | |
result.append(cleaned_cells) | |
return result | |
def XMLAttributesToObject(string, node): | |
doc = ET.fromstring(string.strip()) | |
attr = {} | |
for item in doc.findall(node): | |
for attr_name, attr_value in item.attrib.items(): | |
attr[attr_name] = attr_value | |
return attr | |
def XMLAttributesToObjects(string, node): | |
doc = ET.fromstring(string.strip()) | |
obj=[] | |
attr = {} | |
for item in doc.findall(node): | |
attr = {} | |
for attr_name, attr_value in item.attrib.items(): | |
attr[attr_name] = attr_value | |
obj.append(attr) | |
return obj | |
def extractCookiesFromString(s): | |
# Split the string into individual key-value pairs using "; " as the separator | |
key_value_pairs = s.split('; ') | |
# Initialize an empty dictionary to hold the key-value pairs | |
result_dict = {} | |
# Process each key-value pair | |
for pair in key_value_pairs: | |
# Split on the first '=' to separate the key and value | |
key, value = pair.split('=', 1) | |
result_dict[key.strip()] = value.strip() # Add to the dictionary with stripped keys and values | |
return result_dict | |
global output | |
parser = argparse.ArgumentParser( | |
prog = 'SFR Box status', | |
description = 'Print advanced status of the SFR internet box.' | |
) | |
parser.add_argument('-u', '--user', | |
action='store', | |
help='username of the internet box') | |
parser.add_argument('-p', '--pass', | |
action='store', | |
help='password of the username') | |
parser.add_argument('url', | |
help='url of the internet box (default: %(default)s)', | |
default='http://192.168.1.1', nargs='?', | |
action='store' ) | |
parser.add_argument('--minimal', | |
action='store_true', | |
default=False, | |
help='retreive minimal information') | |
parser.add_argument('--bandwidth', | |
action='store_true', | |
default=False, | |
help='compute bandwidth based on last report stored in --output FILE') | |
parser.add_argument('--extra', | |
action='store', | |
type=lambda s: [str(item) for item in s.split(',')], | |
default=None, | |
help='retreive extra informations. Use a , separated list within "portForwarding"') | |
parser.add_argument('--call', | |
action='store', | |
default=None, | |
help='call directly the url. with proper authentication, you can retreive directly the json. use "http://192.168.1.1/ss-json/fgw.voice/fgw.voice.callhistory.json"') | |
parser.add_argument('-f', '--format', | |
help='output format : txt, csv, html, json (default: %(default)s)', | |
default='txt', | |
action='store') | |
parser.add_argument('-o', '--output', | |
help='output to a file', | |
action='store') | |
parser.add_argument('--token', | |
help='Use this token', | |
action='store', nargs='?') | |
parser.add_argument('--verbose', | |
action='store_true', | |
help='display all transaction and internal computation') | |
args = parser.parse_args() | |
password = getattr(args, 'pass') # args.pass trigger an error | |
username=args.user | |
VERBOSE_LEVEL=args.verbose | |
timeout = 3 | |
DEBUG("Using box located at " + args.url) | |
token=args.token | |
fmt=args.format | |
output=args.output | |
if (args.bandwidth == True and output == None): | |
print("To calculate badwidth, you must indicate the last report with --output FILE") | |
quit() | |
try: | |
if (args.bandwidth == True): | |
# Open last report and compute bandwidth | |
DEBUG("open "+output+" to compute bandwidth") | |
last_report_time = None | |
last_wanRx = None | |
last_wanTx = None | |
try: | |
with open(output, 'r') as f: | |
for line in f: | |
if line.startswith('current_time:'): | |
last_report_time = datetime.datetime.fromisoformat(line.split(': ')[1].strip()) | |
elif line.startswith('wanRx:'): | |
last_wanRx = int(line.split(': ')[1].strip()) | |
elif line.startswith('wanTx:'): | |
last_wanTx = int(line.split(': ')[1].strip()) | |
os.remove(output) | |
except FileNotFoundError: | |
last_report_time = None | |
DEBUG("Last report on "+str(last_report_time)) | |
DEBUG(" last_wanRx "+str(last_wanRx)) | |
DEBUG(" last_wanTx "+str(last_wanTx)) | |
Box_PrintBeginSection(fmt, "Login") | |
T = time.time() | |
s = requests.Session() # will be use to keep cookie | |
# Get the nonce | |
url = args.url + "/uxfwk.session.loader.js" | |
r = s.get(url=url) | |
sessionid = s.cookies['SESSIONID'] | |
DEBUG("sessionid="+sessionid) | |
#items=re.findall(".*nonce:.*$",r.text,re.MULTILINE) | |
match=re.search(r"\bnonce:\s*'([^']*)'", r.text) | |
if (match): | |
nonce = match.group(1) | |
else: | |
nonce = None | |
DEBUG("nonce="+nonce) | |
# compute hash | |
sha256_usr=hashlib.sha256(bytes(username,'UTF-8')).hexdigest() | |
hmac_usr = hmac.new(bytes(nonce,'UTF-8'), digestmod='sha256') | |
hmac_usr.update(sha256_usr.encode()) | |
sha256_pass=hashlib.sha256(bytes(password,'UTF-8')).hexdigest() | |
hmac_pass = hmac.new(bytes(nonce,'UTF-8'), digestmod='sha256') | |
hmac_pass.update(sha256_pass.encode()) | |
credentials = hashlib.sha256(bytes(hmac_usr.hexdigest() + hmac_pass.hexdigest(), 'UTF-8')).hexdigest(); | |
s.headers['Authorization'] = 'Digest '+credentials | |
# send hash during the login | |
DEBUG('Authorization: Digest '+credentials) | |
url = args.url + "/index.html" | |
r = s.get(url=url) | |
xsrf = s.cookies['XSRF-TOKEN'] | |
DEBUG('XSRF-TOKEN'+xsrf) | |
s.headers['X-XSRF-TOKEN'] = xsrf | |
if (args.call != None): | |
url = args.call | |
info = s.get(url=url) | |
print(info.text) | |
quit() | |
if (not args.minimal): | |
# When connected, try to go to http://192.168.1.1/webGUILang.cmd?ptinGUILanguage=FR (should reply {}) | |
url = args.url + "/webGUILang.cmd?ptinGUILanguage=FR" | |
r = s.get(url=url) | |
if (r.text != '{}'): | |
print("Unable to login into the box. Try again in --verbose and see what hapens...", file=sys.stderr) | |
quit() | |
Box_PrintInfo(fmt, 'login_time', str(time.time() - T)) | |
Box_PrintInfo(fmt, 'current_time', str(datetime.datetime.now())) | |
Box_PrintEndSection(fmt, 'Login') | |
if (args.extra is not None and 'portForwarding' in args.extra): | |
Box_PrintBeginSection(fmt, 'PortForwarding_v4') | |
# http://192.168.1.1/ss-json/fgw.security/fgw.natv4.json | |
info = Box_GetJSON(s, args.url + "/ss-json/fgw.security/fgw.natv4.json") | |
# parse the portForwarding struct which is an html table | |
protocol = {} | |
protocol['0'] = 'TCP/UDP' | |
protocol['1'] = 'TCP' | |
protocol['2'] = 'UDP' | |
for port in parse_table(info['portForwarding']): | |
Box_PrintBeginSection(fmt, port[0]) | |
Box_PrintInfo(fmt, "ip", port[6]) | |
Box_PrintInfo(fmt, "protocol", protocol[port[3]]) | |
Box_PrintInfo(fmt, "external", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "internal", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "activated", str(port[10])) | |
Box_PrintEndSection(fmt, port[0]) | |
Box_PrintEndSection(fmt, 'PortForwarding_v4') | |
Box_PrintBeginSection(fmt, 'PortForwarding_v6') | |
info = Box_GetJSON(s, args.url + "/ss-json/fgw.security/fgw.natv6.json") | |
for port in parse_table(info['portForwarding']): | |
Box_PrintBeginSection(fmt, port[0]) | |
Box_PrintInfo(fmt, "ip", port[6]) | |
Box_PrintInfo(fmt, "protocol", protocol[port[3]]) | |
Box_PrintInfo(fmt, "external", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "internal", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "activated", str(port[10])) | |
Box_PrintEndSection(fmt, port[0]) | |
Box_PrintEndSection(fmt, 'PortForwarding_v6') | |
nbDeviceUp = 0 | |
if (not args.minimal): | |
# http://192.168.1.1/ss-json/fgw.summary.json?bypass=0 | |
Box_PrintBeginSection(fmt, 'BoxInfo') | |
info = Box_GetJSON(s, args.url + "/ss-json/fgw.summary.json?bypass=0") | |
Box_PrintInfo(fmt, 'routeSwVersion', info['router']['swVersion']) | |
Box_PrintInfo(fmt, 'routerUptime', info['router']['uptime']) | |
Box_PrintInfo(fmt, 'routerOpticalInterface', info['wan']['opticalInterface']) | |
Box_PrintInfo(fmt, 'lanPortStatus', info['lan']['portStatus']) | |
Box_PrintInfo(fmt, 'wifi0Ssid', info['wifi']['wifi0Ssid']) | |
Box_PrintInfo(fmt, 'wifi1Ssid', info['wifi']['wifi1Ssid']) | |
Box_PrintInfo(fmt, 'voiceStatus', info['voice']['status']) | |
Box_PrintInfo(fmt, 'voiceExtension', info['voice']['extension']) | |
Box_PrintInfo(fmt, 'tvStatus', info['tv']['status']) | |
Box_PrintEndSection(fmt, 'BoxInfo') | |
Box_PrintBeginSection(fmt, 'BoxInfoExtra') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.home.json?bypass=0') | |
Box_PrintInfo(fmt, 'routerName', info['router']['name']) | |
Box_PrintInfo(fmt, 'wifi0GuestStatus', info['wifi']['statusWl0guest']) | |
Box_PrintInfo(fmt, 'wifi1GuestStatus', info['wifi']['statusWl1guest']) | |
Box_PrintEndSection(fmt, 'BoxInfoExtra') | |
Box_PrintBeginSection(fmt, 'WifiGuest') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.wifi.json') | |
Box_PrintInfo(fmt, 'wifiGuestSsid', info['wl0']['guest']['ssid']) | |
Box_PrintEndSection(fmt, 'WifiGuest') | |
# http://192.168.1.1/ss-json/fgw.router.json | |
Box_PrintBeginSection(fmt, 'RouterDeviceInfo') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.router.json') | |
Box_PrintInfo(fmt, 'routerName', info['deviceInfo']['name']) | |
Box_PrintInfo(fmt, 'routeSwVersion', info['deviceInfo']['swVersion']) | |
Box_PrintInfo(fmt, 'routerUptime', info['deviceInfo']['uptime']) | |
Box_PrintInfo(fmt, 'linkStatus', info['deviceInfo']['linkStatus']) | |
Box_PrintInfo(fmt, 'routerOpticalInterface', info['opticalInterface']) | |
Box_PrintEndSection(fmt, 'RouterDeviceInfo') | |
Box_PrintBeginSection(fmt, 'WANStatistics') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.wan/fgw.wanstatistics.json') | |
Box_PrintInfo(fmt, 'wanRx', str(info['statistics'][0]['rx_b'])) | |
Box_PrintInfo(fmt, 'wanTx', str(info['statistics'][0]['tx_b'])) | |
bw_rx = None | |
bw_tx = None | |
if (args.bandwidth == True and last_report_time != None): | |
timediff = (datetime.datetime.now() - last_report_time).total_seconds() | |
bw_rx = int((info['statistics'][0]['rx_b'] - last_wanRx) / timediff) | |
bw_tx = int((info['statistics'][0]['tx_b'] - last_wanTx) / timediff) | |
Box_PrintInfo(fmt, 'wanRxBandwidth', str(bw_rx)) | |
Box_PrintInfo(fmt, 'wanTxBandwidth', str(bw_tx)) | |
Box_PrintEndSection(fmt, 'WANStatistics') | |
Box_PrintBeginSection(fmt, 'LANDevices') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.common/fgw.common.devicesEth.json?bypass=0') # http://192.168.1.1/ss-json/fgw.common/fgw.common.devicesEth.json?bypass=0 | |
i = 1 | |
for d in info['devices']: | |
Box_PrintBeginSection(fmt, 'LanDevice'+str(i)) | |
Box_PrintInfo(fmt, 'name', d['name']) | |
Box_PrintInfo(fmt, 'ipv4', d['ipv4']) | |
Box_PrintInfo(fmt, 'ipv6', d['ipv6']) | |
Box_PrintInfo(fmt, 'mac', d['mac']) | |
Box_PrintInfo(fmt, 'intf', d['intf']) | |
Box_PrintInfo(fmt, 'deviceStatus', str(d['status'])) | |
if (d['status'] == 1): | |
nbDeviceUp = nbDeviceUp + 1 | |
Box_PrintEndSection(fmt, 'LanDevice'+str(i)) | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'LANDevices') | |
# http://192.168.1.1/ss-json/fgw.lan/fgw.lanstatistics.json | |
if (not args.minimal): | |
Box_PrintBeginSection(fmt, 'LANStatistics') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.lan/fgw.lanstatistics.json') | |
i = 1 | |
for stat in info['stats']: | |
Box_PrintBeginSection(fmt, 'Interface'+str(i)) | |
Box_PrintInfo(fmt, 'intf', stat['intf']) | |
Box_PrintInfo(fmt, 'rx', str(stat['rx_b'])) | |
Box_PrintInfo(fmt, 'tx', str(stat['tx_b'])) | |
Box_PrintEndSection(fmt, 'Interface'+str(i)) | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'LANStatistics') | |
# http://192.168.1.1/ss-json/fgw.common/fgw.common.devicesWifi.json?bypass=0 | |
Box_PrintBeginSection(fmt, 'WifiDevices') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.common/fgw.common.devicesWifi.json') | |
i = 1 | |
for d in info['devices']: | |
Box_PrintBeginSection(fmt, 'WifiDevice'+str(i)) | |
Box_PrintInfo(fmt, 'name', d['name']) | |
Box_PrintInfo(fmt, 'ipv4', d['ipv4']) | |
Box_PrintInfo(fmt, 'ipv6', d['ipv6']) | |
Box_PrintInfo(fmt, 'mac', d['mac']) | |
Box_PrintInfo(fmt, 'intf', d['intf']) | |
Box_PrintInfo(fmt, 'deviceStatus', str(d['status'])) | |
Box_PrintEndSection(fmt, 'WifiDevice'+str(i)) | |
if (d['status'] == 1): | |
nbDeviceUp = nbDeviceUp + 1 | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'WifiDevices') | |
if (not args.minimal): | |
Box_PrintBeginSection(fmt, 'WifiStatistics') | |
#http://192.168.1.1/ss-json/fgw.wifi/fgw.wifistatistics.json | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.wifi/fgw.wifistatistics.json') | |
i = 1 | |
for stat in info['stats']: | |
Box_PrintBeginSection(fmt, 'Interface'+str(i)) | |
Box_PrintInfo(fmt, 'intf', stat['intf']) | |
Box_PrintInfo(fmt, 'rx', str(stat['rx_b'])) | |
Box_PrintInfo(fmt, 'tx', str(stat['tx_b'])) | |
Box_PrintEndSection(fmt, 'Interface'+str(i)) | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'WifiStatistics') | |
Box_PrintBeginSection(fmt, 'OtherInformation') | |
Box_PrintInfo(fmt, 'nbDeviceUp', str(nbDeviceUp)) | |
Box_PrintInfo(fmt, 'timeToComplete', str(time.time() - T)) | |
Box_PrintEndSection(fmt, 'OtherInformation') | |
except requests.exceptions.Timeout as e: | |
print("TimeoutError! :" + str(e)) | |
except requests.exceptions.TooManyRedirects as e: | |
print("Bad URL with too many redirection! :" + str(e)) | |
except requests.exceptions.RequestException as e: | |
print("Request exception! :" + str(e)) | |
except requests.exceptions.HTTPError as e: | |
print("HTTP error! :" + str(e)) | |
except ValueError as e: | |
print(str(e)) | |
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
''' | |
Inspired by https://lafibre.info/sfr-les-news/nb6vac-et-api/ | |
Compatible with SFR BOX FIBRE 7 (Router name : GR140CG) | |
Usage : python3 rbox_status.ph -u admin -p password | |
todo : | |
implement read of NAT rules : https://lafibre.info/sfr-les-news/spec-api-rest-box-de-sfr/msg1118513/#msg1118513 | |
''' | |
import hashlib | |
import hmac | |
import requests | |
import re | |
import time | |
import argparse | |
import pprint | |
import json | |
import sys | |
#import xml.etree.ElementTree as ET | |
def DEBUG(text: str): | |
if (VERBOSE_LEVEL==True): | |
print(text) | |
def _compute_hash(token: str, value: str) -> str: | |
"""Compute single value hash.""" | |
hash = hashlib.sha256(value.encode()).hexdigest() | |
return hmac.new(token.encode(), hash.encode(), hashlib.sha256).hexdigest() | |
def compute_hash(token: str, username: str, password: str) -> str: | |
# the original code will not work for the web interface. | |
# For the API, it is hash(u)+hash(p), | |
# but the web interface use a hash(hash(u)+hash(p)) | |
"""Compute full username/password hash.""" | |
hmac_username = _compute_hash(token, username) | |
hmac_password = _compute_hash(token, password) | |
return f"{hmac_username}{hmac_password}" | |
def Box_GetJSON(session, url): | |
r = session.get(url) | |
j = json.loads(r.text) | |
return j | |
SECTION_INC=0 | |
def Box_PrintBeginSection(fmt, name): | |
global SECTION_INC | |
global output | |
f = sys.stdout if output == None else open(output, "a") | |
if (fmt == 'txt'): | |
print(">" * (3*SECTION_INC) + ">>> " + name, file=f) | |
SECTION_INC = SECTION_INC + 1 | |
def Box_PrintEndSection(fmt, name): | |
global SECTION_INC | |
global output | |
f = sys.stdout if output == None else open(output, "a") | |
if (fmt == 'txt'): | |
print("", file=f) | |
SECTION_INC = SECTION_INC - 1 | |
def Box_PrintInfo(fmt, info, value): | |
global output | |
f = sys.stdout if output == None else open(output, "a") | |
if (fmt == 'txt'): | |
print(info+": "+value.strip(), file=f) | |
return | |
def parse_table(s): | |
# Use regex to find all rows in the string | |
rows = re.findall(r'<tr[^>]*>(.*?)</tr>', s, re.DOTALL) | |
result = [] | |
for row in rows: | |
# Find all cells in the row | |
cells = re.findall(r'<td[^>]*>(.*?)</td>', row, re.DOTALL) | |
# Clean up the cell data by stripping whitespace and HTML tags | |
cleaned_cells = [re.sub(r'<.*?>', '', cell).strip() for cell in cells] | |
result.append(cleaned_cells) | |
return result | |
def XMLAttributesToObject(string, node): | |
doc = ET.fromstring(string.strip()) | |
attr = {} | |
for item in doc.findall(node): | |
for attr_name, attr_value in item.attrib.items(): | |
attr[attr_name] = attr_value | |
return attr | |
def XMLAttributesToObjects(string, node): | |
doc = ET.fromstring(string.strip()) | |
obj=[] | |
attr = {} | |
for item in doc.findall(node): | |
attr = {} | |
for attr_name, attr_value in item.attrib.items(): | |
attr[attr_name] = attr_value | |
obj.append(attr) | |
return obj | |
def extractCookiesFromString(s): | |
# Split the string into individual key-value pairs using "; " as the separator | |
key_value_pairs = s.split('; ') | |
# Initialize an empty dictionary to hold the key-value pairs | |
result_dict = {} | |
# Process each key-value pair | |
for pair in key_value_pairs: | |
# Split on the first '=' to separate the key and value | |
key, value = pair.split('=', 1) | |
result_dict[key.strip()] = value.strip() # Add to the dictionary with stripped keys and values | |
return result_dict | |
global output | |
parser = argparse.ArgumentParser( | |
prog = 'SFR Box status', | |
description = 'Print advanced status of the SFR internet box.' | |
) | |
parser.add_argument('-u', '--user', | |
action='store', | |
help='username of the internet box') | |
parser.add_argument('-p', '--pass', | |
action='store', | |
help='password of the username') | |
parser.add_argument('url', | |
help='url of the internet box (default: %(default)s)', | |
default='http://192.168.1.1', nargs='?', | |
action='store' ) | |
parser.add_argument('--minimal', | |
action='store_true', | |
default=False, | |
help='retreive minimal information') | |
parser.add_argument('--extra', | |
action='store', | |
type=lambda s: [str(item) for item in s.split(',')], | |
default=None, | |
help='retreive extra informations. Use a , separated list within "portForwarding"') | |
parser.add_argument('--call', | |
action='store', | |
default=None, | |
help='call directly the url. with proper authentication, you can retreive directly the json. use "http://192.168.1.1/ss-json/fgw.voice/fgw.voice.callhistory.json"') | |
parser.add_argument('-f', '--format', | |
help='output format : txt, csv, html, json (default: %(default)s)', | |
default='txt', | |
action='store') | |
parser.add_argument('-o', '--output', | |
help='output to a file', | |
action='store') | |
parser.add_argument('--token', | |
help='Use this token', | |
action='store', nargs='?') | |
parser.add_argument('--verbose', | |
action='store_true', | |
help='display all transaction and internal computation') | |
args = parser.parse_args() | |
password = getattr(args, 'pass') # args.pass trigger an error | |
username=args.user | |
VERBOSE_LEVEL=args.verbose | |
timeout = 3 | |
DEBUG("Using box located at " + args.url) | |
token=args.token | |
fmt=args.format | |
output=args.output | |
try: | |
Box_PrintBeginSection(fmt, "Login") | |
T = time.time() | |
s = requests.Session() # will be use to keep cookie | |
# Get the nonce | |
url = args.url + "/uxfwk.session.loader.js" | |
r = s.get(url=url) | |
sessionid = s.cookies['SESSIONID'] | |
DEBUG("sessionid="+sessionid) | |
#items=re.findall(".*nonce:.*$",r.text,re.MULTILINE) | |
match=re.search(r"\bnonce:\s*'([^']*)'", r.text) | |
if (match): | |
nonce = match.group(1) | |
else: | |
nonce = None | |
DEBUG("nonce="+nonce) | |
# compute hash | |
sha256_usr=hashlib.sha256(bytes(username,'UTF-8')).hexdigest() | |
hmac_usr = hmac.new(bytes(nonce,'UTF-8'), digestmod='sha256') | |
hmac_usr.update(sha256_usr.encode()) | |
sha256_pass=hashlib.sha256(bytes(password,'UTF-8')).hexdigest() | |
hmac_pass = hmac.new(bytes(nonce,'UTF-8'), digestmod='sha256') | |
hmac_pass.update(sha256_pass.encode()) | |
credentials = hashlib.sha256(bytes(hmac_usr.hexdigest() + hmac_pass.hexdigest(), 'UTF-8')).hexdigest(); | |
s.headers['Authorization'] = 'Digest '+credentials | |
# send hash during the login | |
DEBUG('Authorization: Digest '+credentials) | |
url = args.url + "/index.html" | |
r = s.get(url=url) | |
xsrf = s.cookies['XSRF-TOKEN'] | |
DEBUG('XSRF-TOKEN'+xsrf) | |
s.headers['X-XSRF-TOKEN'] = xsrf | |
if (args.call != None): | |
url = args.call | |
info = s.get(url=url) | |
print(info.text) | |
quit() | |
if (not args.minimal): | |
# When connected, try to go to http://192.168.1.1/webGUILang.cmd?ptinGUILanguage=FR (should reply {}) | |
url = args.url + "/webGUILang.cmd?ptinGUILanguage=FR" | |
r = s.get(url=url) | |
if (r.text != '{}'): | |
print("Unable to login into the box. Try again in --verbose and see what hapens...", file=sys.stderr) | |
quit() | |
Box_PrintInfo(fmt, 'login_time', str(time.time() - T)) | |
Box_PrintEndSection(fmt, 'Login') | |
if (args.extra is not None and 'portForwarding' in args.extra): | |
Box_PrintBeginSection(fmt, 'PortForwarding_v4') | |
# http://192.168.1.1/ss-json/fgw.security/fgw.natv4.json | |
info = Box_GetJSON(s, args.url + "/ss-json/fgw.security/fgw.natv4.json") | |
# parse the portForwarding struct which is an html table | |
protocol = {} | |
protocol['0'] = 'TCP/UDP' | |
protocol['1'] = 'TCP' | |
protocol['2'] = 'UDP' | |
for port in parse_table(info['portForwarding']): | |
Box_PrintBeginSection(fmt, port[0]) | |
Box_PrintInfo(fmt, "ip", port[6]) | |
Box_PrintInfo(fmt, "protocol", protocol[port[3]]) | |
Box_PrintInfo(fmt, "external", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "internal", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "activated", str(port[10])) | |
Box_PrintEndSection(fmt, port[0]) | |
Box_PrintEndSection(fmt, 'PortForwarding_v4') | |
Box_PrintBeginSection(fmt, 'PortForwarding_v6') | |
info = Box_GetJSON(s, args.url + "/ss-json/fgw.security/fgw.natv6.json") | |
for port in parse_table(info['portForwarding']): | |
Box_PrintBeginSection(fmt, port[0]) | |
Box_PrintInfo(fmt, "ip", port[6]) | |
Box_PrintInfo(fmt, "protocol", protocol[port[3]]) | |
Box_PrintInfo(fmt, "external", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "internal", str(port[1]) + '-'+str(port[2])) | |
Box_PrintInfo(fmt, "activated", str(port[10])) | |
Box_PrintEndSection(fmt, port[0]) | |
Box_PrintEndSection(fmt, 'PortForwarding_v6') | |
nbDeviceUp = 0 | |
if (not args.minimal): | |
# http://192.168.1.1/ss-json/fgw.summary.json?bypass=0 | |
Box_PrintBeginSection(fmt, 'BoxInfo') | |
info = Box_GetJSON(s, args.url + "/ss-json/fgw.summary.json?bypass=0") | |
Box_PrintInfo(fmt, 'routeSwVersion', info['router']['swVersion']) | |
Box_PrintInfo(fmt, 'routerUptime', info['router']['uptime']) | |
Box_PrintInfo(fmt, 'routerOpticalInterface', info['wan']['opticalInterface']) | |
Box_PrintInfo(fmt, 'lanPortStatus', info['lan']['portStatus']) | |
Box_PrintInfo(fmt, 'wifi0Ssid', info['wifi']['wifi0Ssid']) | |
Box_PrintInfo(fmt, 'wifi1Ssid', info['wifi']['wifi1Ssid']) | |
Box_PrintInfo(fmt, 'voiceStatus', info['voice']['status']) | |
Box_PrintInfo(fmt, 'voiceExtension', info['voice']['extension']) | |
Box_PrintInfo(fmt, 'tvStatus', info['tv']['status']) | |
Box_PrintEndSection(fmt, 'BoxInfo') | |
Box_PrintBeginSection(fmt, 'BoxInfoExtra') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.home.json?bypass=0') | |
Box_PrintInfo(fmt, 'routerName', info['router']['name']) | |
Box_PrintInfo(fmt, 'wifi0GuestStatus', info['wifi']['statusWl0guest']) | |
Box_PrintInfo(fmt, 'wifi1GuestStatus', info['wifi']['statusWl1guest']) | |
Box_PrintEndSection(fmt, 'BoxInfoExtra') | |
Box_PrintBeginSection(fmt, 'WifiGuest') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.wifi.json') | |
Box_PrintInfo(fmt, 'wifiGuestSsid', info['wl0']['guest']['ssid']) | |
Box_PrintEndSection(fmt, 'WifiGuest') | |
# http://192.168.1.1/ss-json/fgw.router.json | |
Box_PrintBeginSection(fmt, 'RouterDeviceInfo') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.router.json') | |
Box_PrintInfo(fmt, 'routerName', info['deviceInfo']['name']) | |
Box_PrintInfo(fmt, 'routeSwVersion', info['deviceInfo']['swVersion']) | |
Box_PrintInfo(fmt, 'routerUptime', info['deviceInfo']['uptime']) | |
Box_PrintInfo(fmt, 'linkStatus', info['deviceInfo']['linkStatus']) | |
Box_PrintInfo(fmt, 'routerOpticalInterface', info['opticalInterface']) | |
Box_PrintEndSection(fmt, 'RouterDeviceInfo') | |
Box_PrintBeginSection(fmt, 'WANStatistics') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.wan/fgw.wanstatistics.json') | |
Box_PrintInfo(fmt, 'wanRx', str(info['statistics'][0]['rx_b'])) | |
Box_PrintInfo(fmt, 'wanTx', str(info['statistics'][0]['tx_b'])) | |
Box_PrintEndSection(fmt, 'WANStatistics') | |
Box_PrintBeginSection(fmt, 'LANDevices') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.common/fgw.common.devicesEth.json?bypass=0') # http://192.168.1.1/ss-json/fgw.common/fgw.common.devicesEth.json?bypass=0 | |
i = 1 | |
for d in info['devices']: | |
Box_PrintBeginSection(fmt, 'LanDevice'+str(i)) | |
Box_PrintInfo(fmt, 'name', d['name']) | |
Box_PrintInfo(fmt, 'ipv4', d['ipv4']) | |
Box_PrintInfo(fmt, 'ipv6', d['ipv6']) | |
Box_PrintInfo(fmt, 'mac', d['mac']) | |
Box_PrintInfo(fmt, 'intf', d['intf']) | |
Box_PrintInfo(fmt, 'deviceStatus', str(d['status'])) | |
if (d['status'] == 1): | |
nbDeviceUp = nbDeviceUp + 1 | |
Box_PrintEndSection(fmt, 'LanDevice'+str(i)) | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'LANDevices') | |
# http://192.168.1.1/ss-json/fgw.lan/fgw.lanstatistics.json | |
if (not args.minimal): | |
Box_PrintBeginSection(fmt, 'LANStatistics') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.lan/fgw.lanstatistics.json') | |
i = 1 | |
for stat in info['stats']: | |
Box_PrintBeginSection(fmt, 'Interface'+str(i)) | |
Box_PrintInfo(fmt, 'intf', stat['intf']) | |
Box_PrintInfo(fmt, 'rx', str(stat['rx_b'])) | |
Box_PrintInfo(fmt, 'tx', str(stat['tx_b'])) | |
Box_PrintEndSection(fmt, 'Interface'+str(i)) | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'LANStatistics') | |
# http://192.168.1.1/ss-json/fgw.common/fgw.common.devicesWifi.json?bypass=0 | |
Box_PrintBeginSection(fmt, 'WifiDevices') | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.common/fgw.common.devicesWifi.json') | |
i = 1 | |
for d in info['devices']: | |
Box_PrintBeginSection(fmt, 'WifiDevice'+str(i)) | |
Box_PrintInfo(fmt, 'name', d['name']) | |
Box_PrintInfo(fmt, 'ipv4', d['ipv4']) | |
Box_PrintInfo(fmt, 'ipv6', d['ipv6']) | |
Box_PrintInfo(fmt, 'mac', d['mac']) | |
Box_PrintInfo(fmt, 'intf', d['intf']) | |
Box_PrintInfo(fmt, 'deviceStatus', str(d['status'])) | |
Box_PrintEndSection(fmt, 'WifiDevice'+str(i)) | |
if (d['status'] == 1): | |
nbDeviceUp = nbDeviceUp + 1 | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'WifiDevices') | |
if (not args.minimal): | |
Box_PrintBeginSection(fmt, 'WifiStatistics') | |
#http://192.168.1.1/ss-json/fgw.wifi/fgw.wifistatistics.json | |
info = Box_GetJSON(s, args.url + '/ss-json/fgw.wifi/fgw.wifistatistics.json') | |
i = 1 | |
for stat in info['stats']: | |
Box_PrintBeginSection(fmt, 'Interface'+str(i)) | |
Box_PrintInfo(fmt, 'intf', stat['intf']) | |
Box_PrintInfo(fmt, 'rx', str(stat['rx_b'])) | |
Box_PrintInfo(fmt, 'tx', str(stat['tx_b'])) | |
Box_PrintEndSection(fmt, 'Interface'+str(i)) | |
i = i + 1 | |
Box_PrintEndSection(fmt, 'WifiStatistics') | |
Box_PrintBeginSection(fmt, 'OtherInformation') | |
Box_PrintInfo(fmt, 'nbDeviceUp', str(nbDeviceUp)) | |
Box_PrintInfo(fmt, 'timeToComplete', str(time.time() - T)) | |
Box_PrintEndSection(fmt, 'OtherInformation') | |
except requests.exceptions.Timeout as e: | |
print("TimeoutError! :" + str(e)) | |
except requests.exceptions.TooManyRedirects as e: | |
print("Bad URL with too many redirection! :" + str(e)) | |
except requests.exceptions.RequestException as e: | |
print("Request exception! :" + str(e)) | |
except requests.exceptions.HTTPError as e: | |
print("HTTP error! :" + str(e)) | |
except ValueError as e: | |
print(str(e)) |
New cool option to view NAT rules :
$> python3 rbox_status2.py -u admin -p password--extra=portForwarding --minimal
>>> Login
login_time: 2.9362215995788574
>>> PortForwarding_v4
>>>>>> TESTJFO
ip: 192.168.1.254
protocol: TCP/UDP
external: 123-123
internal: 123-123
activated: 1
>>>>>> Test2
ip: 192.168.1.251
protocol: TCP
external: 100-101
internal: 100-101
activated: 0
>>> PortForwarding_v6
>>> RouterDeviceInfo
routerName: GR140CG
routeSwVersion: 3ENT010201R00C
routerUptime: 3d 3h 35m 56s
linkStatus: 5
routerOpticalInterface: Up|-26.0dBm|4.3dBm|NOS
(...)
New cool option to call any API / URL of the box :
python3 rbox_status2.py -u admin -p password --call http://192.168.1.1/ss-json/fgw.voice/fgw.voice.callhistory.json
>>> Login
{
"statusHistory": "1",
"data": { "callHistory": [ { "accountNumber":"0", "statistics": [ { "date":"1749570992","type":"IN","destination":"+33663599959","duration":"0d 0h 0m 25s" }, { "date":"-3600","type":"-","destination":"-","duration":"0d 0h 0m 25s" }, { "date":"1749571044","type":"OUT","destination":"0139789290","duration":"0d 0h 0m 5s" }, { "date":"1749571162","type":"OUT","destination":"0139789290","duration":"0d 0h 0m 22s" }, { "date":"1749571451","type":"OUT","destination":"0139789290","duration":"0d 0h 0m 4s" }, { "date":"1749572234","type":"OUT","destination":"0139789290","duration":"0d 0h 0m 5s" }], "pad" : null } ]},
"pad":null}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Exemple :