From 194540ccd4136271daa1f901acc197da23a25630 Mon Sep 17 00:00:00 2001 From: Gregor Michels Date: Thu, 3 Jul 2025 17:51:42 +0200 Subject: [PATCH 1/2] did things --- __pycache__/main.cpython-311.pyc | Bin 0 -> 12457 bytes main.py | 312 +++++++++++++++++++++++++------ 2 files changed, 254 insertions(+), 58 deletions(-) create mode 100644 __pycache__/main.cpython-311.pyc diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..460408db1a73cd3150f2f679bcacc59094e6e8c5 GIT binary patch literal 12457 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 str: + """Generates an obfuscated password from a cleartext pw""" + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" result = "" l = len(pw) @@ -56,7 +98,98 @@ def zyxel_password(pw: str) -> str: return result +def dictify(data: list, key: str) -> dict: + """ + converts a list of dictionaries to a single dictionary by using + the `key`'s value of each element as the new key + """ + result = {} + for d in data: + k = d[key] + del d[key] + result.update({k: d}) + return result + + +# ============================================================================== +# Parsing functions +# ============================================================================== + + +def parse_xssid(response: str) -> str: + """parses a xssid value from html source containing it""" + soup = BeautifulSoup(response, "html.parser") + xssid_input = soup.find_all("input", attrs={"type": "hidden", "name": "XSSID"}) + try: + inp = xssid_input[0] + except IndexError: + return None + return inp.attrs.get("value") + + +def parse_vlan_ports(response: str) -> dict: + """parses the response of a READ_VLAN_PORTS get-command""" + soup = BeautifulSoup(response, "html.parser") + + data = {} + port_settings = soup.find_all( + "input", attrs={"type": "hidden", "id": re.compile(r"vlanMode_\d+")} + ) + for port_setting in port_settings: + port = port_setting.find_previous().text.strip() + port_type = int(port_setting.attrs.get("value")) + + acl = list( + filter( + lambda p: "checked" in p.attrs, + port_setting.find_next().find_all("input"), + ) + )[0] + + data.update( + { + port: { + "mode": PORT_VLAN_MODE(port_type), + "acl": PORT_VLAN_ACL(int(acl.attrs.get("value"))), + } + } + ) + + return data + + +def extract_data_from_table(response: str) -> list: + """ + parses the _last_ table from a HTML text and returns a list of dicts, each + dict contains one line from the table + """ + soup = BeautifulSoup(response, "html.parser") + table_body = soup.find_all("table")[-1] + + rows = table_body.find_all("tr") + keys = [ele.text.strip() for ele in rows[0].find_all("td")] + entries = [] + for row in rows[1:]: + cols = row.find_all("td") + cols = [ele.text.strip() for ele in cols] + entries.append({k: v for (k, v) in zip(keys, cols) if k.strip() != ""}) + + return list(filter(lambda e: e, entries)) + + +# ============================================================================== +# "API" request functions +# ============================================================================== + + def get_login_cookie(host: str, username: str, password: str) -> str: + """ + logs into the webservice using username and password for authentication. + + - `host` - hostname or IP address of the device + - `username` - username for login + - `password` - cleartext password, it will be encoded using `zyxel_password` + """ auth_id = requests.get( f"http://{host}/cgi-bin/dispatcher.cgi", params={"login": 1, "username": username, "password": zyxel_password(password)}, @@ -79,6 +212,7 @@ def get_login_cookie(host: str, username: str, password: str) -> str: def get_cmd(host: str, xssid: str, cmd: int, **params) -> str: + """sends a get command, returns the plaintext answer""" params.update({"cmd": cmd}) return requests.get( f"http://{host}/cgi-bin/dispatcher.cgi", @@ -87,68 +221,130 @@ def get_cmd(host: str, xssid: str, cmd: int, **params) -> str: ).text -def parse_vlan_ports(response: str) -> dict: - soup = BeautifulSoup(response, "html.parser") +def set_cmd(host: str, xssid: str, cmd: int, **params) -> str: + """ + sends a set command, returns the plaintext answer. + You can supply params for the command as keyword arguments. + """ + # inject command into data + params.update({"cmd": cmd}) - data = {} - port_settings = soup.find_all( - "input", attrs={"type": "hidden", "id": re.compile(r"vlanMode_\d+")} + # get "CSRF" token for submitting a form + token = parse_xssid(get_cmd(host, xssid, API_CMD_IDS.READ_SYS_IP.value)) + params.update({"XSSID": token}) + if token is None: + raise Exception("unable to get XSSID token") + + # call api + return requests.post( + f"http://{host}/cgi-bin/dispatcher.cgi", + data=params, + cookies={"HTTP_XSSID": xssid}, + ).text + + +# ============================================================================== +# Set command wrapper functions +# ============================================================================== + + +def update_port( + host: str, + xssid: str, + port: str, + description: str, + state: PORT_STATE, + speed: PORT_SPEED, + duplex: PORT_DUPLEX, + fc: PORT_FLOW_CONTROL, +): + """configures a port, returns true on success""" + return "window.location.replace" in set_cmd( + host, + xssid, + API_CMD_IDS.WRITE_PORT.value, + portlist=port, + descp=description, + state=str(state.value), + speed=str(speed.value), + duplex=str(duplex.value), + fc=str(fc.value), + sysSubmit="Apply", ) - for port_setting in port_settings: - port = port_setting.find_previous().text.strip() - port_type = int(port_setting.attrs.get("value")) - - acl = list( - filter( - lambda p: "checked" in p.attrs, - port_setting.find_next().find_all("input"), - ) - )[0] - - data.update( - { - port: { - "mode": VLAN_PORT_MODE(port_type), - "acl": VLAN_PORT_ACL(int(acl.attrs.get("value"))), - } - } - ) - - return data -def extract_data_from_table(response: str) -> dict: - soup = BeautifulSoup(response, "html.parser") - - data = {} - table_body = soup.find_all("table")[-1] - - rows = table_body.find_all("tr") - keys = [ele.text.strip() for ele in rows[0].find_all("td")] - entries = [] - for row in rows[1:]: - cols = row.find_all("td") - cols = [ele.text.strip() for ele in cols] - entries.append({k: v for (k, v) in zip(keys, cols) if k.strip() != ""}) - - return list(filter(lambda e: e, entries)) +def create_vlan(host: str, cookie: str, vlan_id: int, vlan_name_prefix="VLAN"): + return "window.location.replace" in set_cmd( + host, + cookie, + API_CMD_IDS.CREATE_VLAN.value, + vlanlist=str(vlan_id), + vlanAction="0", + name=vlan_name_prefix, + sysSubmit="Apply", + ) -def dictify(data: list, key: str) -> dict: - result = {} - for d in data: - k = d[key] - del d[key] - result.update({k: d}) - return result +def update_vlan_name(host: str, cookie: str, vlan_id: int, vlan_name: str): + return "window.location.replace" in set_cmd( + host, + cookie, + API_CMD_IDS.UPDATE_VLAN.value, + vidValue=str(vlan_id), + editName=vlan_name, + sysSubmit="Apply", + ) + + +def delete_vlan(host: str, cookie: str, vlan_id: int): + return "window.location.replace" in get_cmd( + host, cookie, API_CMD_IDS.DELETE_VLAN.value, _del=vlan_id + ) + + +def update_port_vlan(host: str, cookie: str, port: str): + pass + + +# ============================================================================== +# Get command wrapper functions +# ============================================================================== + + +def read_ports(host: str, xssid: str) -> dict: + return extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_PORTS.value)) + + +def read_vlans(host: str, xssid: str) -> dict: + return extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_VLANS.value)) + + +def read_vlan_ports(host: str, xssid: str) -> dict: + return extract_data_from_table( + get_cmd(host, xssid, API_CMD_IDS.READ_VLAN_PORTS.value) + ) + + +def read_stp_ports(host: str, xssid: str) -> dict: + return extract_data_from_table( + get_cmd(host, xssid, API_CMD_IDS.READ_STP_PORTS.value) + ) + + +def read_vlan_port_cfg(host: str, xssid: str, vid: int) -> dict: + return parse_vlan_ports( + get_cmd(host, xssid, API_CMD_IDS.READ_VLAN_PORT_CFG.value, vid=vid) + ) if __name__ == "__main__": - + IP = "192.168.42.100" cookie = get_login_cookie(IP, "admin", os.environ.get("ADMIN_PW")) - print(dictify(extract_data_from_table(get_cmd(IP, cookie, API_CMD_IDS.READ_PORTS.value)), "Port")) - print(dictify(extract_data_from_table(get_cmd(IP, cookie, API_CMD_IDS.READ_VLANS.value)), "VLAN ID")) - print(dictify(extract_data_from_table(get_cmd(IP, cookie, API_CMD_IDS.READ_VLAN_PORTS.value)), "Port")) - print(dictify(extract_data_from_table(get_cmd(IP, cookie, API_CMD_IDS.READ_STP_PORTS.value)), "Port")) - print(parse_vlan_ports(get_cmd(IP, cookie, API_CMD_IDS.READ_VLAN_PORT_CFG.value, vid=2))) + # print(delete_vlan(IP, cookie, 423)) + # print(delete_vlan(IP, cookie, 111)) + # print(delete_vlan(IP, cookie, 666)) + # + # print(update_vlan_name(IP, cookie, 2009, "customer_2009")) + + print(read_vlan_port_cfg(IP, cookie, 2)) From 510d8c73213e715f109cc8ec4eb5eea0f97cd3df Mon Sep 17 00:00:00 2001 From: Gregor Michels Date: Thu, 3 Jul 2025 18:10:17 +0200 Subject: [PATCH 2/2] dicitfy read_ commands --- main.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index 5e3f7ad..61e7ac1 100755 --- a/main.py +++ b/main.py @@ -312,28 +312,41 @@ def update_port_vlan(host: str, cookie: str, port: str): def read_ports(host: str, xssid: str) -> dict: - return extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_PORTS.value)) + return dictify( + extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_PORTS.value)), + "Port", + ) def read_vlans(host: str, xssid: str) -> dict: - return extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_VLANS.value)) + return dictify( + extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_VLANS.value)), + "VLAN ID", + ) def read_vlan_ports(host: str, xssid: str) -> dict: - return extract_data_from_table( - get_cmd(host, xssid, API_CMD_IDS.READ_VLAN_PORTS.value) + return dictify( + extract_data_from_table( + get_cmd(host, xssid, API_CMD_IDS.READ_VLAN_PORTS.value) + ), + "Port", ) def read_stp_ports(host: str, xssid: str) -> dict: - return extract_data_from_table( - get_cmd(host, xssid, API_CMD_IDS.READ_STP_PORTS.value) + return dictify( + extract_data_from_table(get_cmd(host, xssid, API_CMD_IDS.READ_STP_PORTS.value)), + "Port", ) def read_vlan_port_cfg(host: str, xssid: str, vid: int) -> dict: - return parse_vlan_ports( - get_cmd(host, xssid, API_CMD_IDS.READ_VLAN_PORT_CFG.value, vid=vid) + return dictify( + parse_vlan_ports( + get_cmd(host, xssid, API_CMD_IDS.READ_VLAN_PORT_CFG.value, vid=vid) + ), + "VLAN", ) @@ -341,10 +354,4 @@ if __name__ == "__main__": IP = "192.168.42.100" cookie = get_login_cookie(IP, "admin", os.environ.get("ADMIN_PW")) - # print(delete_vlan(IP, cookie, 423)) - # print(delete_vlan(IP, cookie, 111)) - # print(delete_vlan(IP, cookie, 666)) - # - # print(update_vlan_name(IP, cookie, 2009, "customer_2009")) - - print(read_vlan_port_cfg(IP, cookie, 2)) + print(read_stp_ports(IP, cookie))