#!/usr/bin/env python3 """Reproduce broken RSC syntax, then import fixed RSC on RB4011 lab via Tailscale.""" from __future__ import annotations import sys import time from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import vps_ssh import paramiko HERE = Path(__file__).resolve().parent JUMP = ("100.80.255.113", "otantik", "DeararbsFr2@") MK = ("172.30.0.1", "admin", "demopass") def wait_prompt(shell, timeout=25) -> str: buf = b"" end = time.time() + timeout while time.time() < end: time.sleep(0.15) if shell.recv_ready(): buf += shell.recv(65535) end = time.time() + 2.0 if b"> " in buf[-250:]: break return buf.decode("utf-8", "replace") def cmd(shell, line: str, timeout=40) -> str: shell.send(line + "\r") out = wait_prompt(shell, timeout) print("\n===== " + line[:100] + " =====") print(out[-4500:]) return out def fetch_live_rsc() -> str: py = HERE / "_tmp_fetch_rsc.py" py.write_text( "import urllib.request\n" "req=urllib.request.Request('http://127.0.0.1/provision/test-enjoy.rsc', headers={'Host':'wifi.otantik.cm'})\n" "data=urllib.request.urlopen(req, timeout=25).read()\n" "open('/tmp/wo-test-enjoy.rsc','wb').write(data)\n" "print(len(data))\n", encoding="utf-8", ) vps_ssh.put(str(py), "/tmp/wo-fetch-rsc.py") code, out = vps_ssh.run("python3 /tmp/wo-fetch-rsc.py") print("live fetch", code, out.strip()) client = vps_ssh.connect() try: sftp = client.open_sftp() local = HERE / "_tmp_live.rsc" sftp.get("/tmp/wo-test-enjoy.rsc", str(local)) sftp.close() finally: client.close() return local.read_text(encoding="utf-8", errors="replace") def make_fixed(text: str) -> str: # same transforms as PHP fix, applied to already-rendered RSC for offline test reps = [ ("/system identity set name=$shortname", '/system identity set name="$shortname"'), ("find name=$wgName", 'find name="$wgName"'), ("find interface=$wgName", 'find interface="$wgName"'), ("name=$wgName ", 'name="$wgName" '), ("private-key=$wgPrivateKey", 'private-key="$wgPrivateKey"'), ("public-key=$wgServerPublicKey", 'public-key="$wgServerPublicKey"'), ("endpoint-address=$wgServerIP", 'endpoint-address="$wgServerIP"'), ("allowed-address=$wgNetwork", 'allowed-address="$wgNetwork"'), ("address=$wgLocalIP", 'address="$wgLocalIP"'), ("interface=$wgName ", 'interface="$wgName" '), ("interface=$wgName\n", 'interface="$wgName"\n'), ('dst-address=$wgNetwork gateway=$wgName', 'dst-address="$wgNetwork" gateway="$wgName"'), ("src-address=$wgServerTunnelIP", 'src-address="$wgServerTunnelIP"'), ("in-interface=$wgName ", 'in-interface="$wgName" '), ("out-interface=$wgName ", 'out-interface="$wgName" '), ] for a, b in reps: text = text.replace(a, b) return text def connect_mk(): jump = paramiko.SSHClient() jump.set_missing_host_key_policy(paramiko.AutoAddPolicy()) jump.connect( JUMP[0], username=JUMP[1], password=JUMP[2], timeout=25, allow_agent=False, look_for_keys=False, ) chan = jump.get_transport().open_channel("direct-tcpip", (MK[0], 22), ("127.0.0.1", 0)) mk = paramiko.SSHClient() mk.set_missing_host_key_policy(paramiko.AutoAddPolicy()) mk.connect( MK[0], username=MK[1], password=MK[2], sock=chan, timeout=25, allow_agent=False, look_for_keys=False, ) return jump, mk def main() -> None: live = fetch_live_rsc() fixed = make_fixed(live) (HERE / "_tmp_fixed.rsc").write_text(fixed, encoding="utf-8") print("live has unquoted priv?", "private-key=$wgPrivateKey" in live) print("fixed has quoted priv?", 'private-key="$wgPrivateKey"' in fixed) jump, mk = connect_mk() shell = mk.invoke_shell(width=220, height=60) wait_prompt(shell, 20) cmd(shell, "/system identity print") cmd(shell, "/system resource print") sftp = mk.open_sftp() with sftp.file("wo-broken.rsc", "w") as f: f.write(live.replace("\r\n", "\n")) with sftp.file("wo-fixed.rsc", "w") as f: f.write(fixed.replace("\r\n", "\n")) sftp.close() print("\n*** IMPORT BROKEN (expect syntax error near WG keys / identity) ***") cmd(shell, "/import file-name=wo-broken.rsc", timeout=90) time.sleep(2) cmd(shell, '/log print where message~"syntax" or message~"expected" or message~"failure"') # cleanup partial WG if any, then import fixed cmd(shell, ':do { /interface wireguard remove [find name="wg-otantik"] } on-error={}') print("\n*** IMPORT FIXED ***") cmd(shell, "/import file-name=wo-fixed.rsc", timeout=120) time.sleep(5) cmd(shell, "/interface wireguard print detail") cmd(shell, "/interface wireguard peers print detail") cmd(shell, "/ip address print where comment=wifi-otantik") cmd(shell, "/ping 10.88.0.1 count=3") cmd(shell, '/log print where message~"syntax" or message~"expected"') mk.close() jump.close() if __name__ == "__main__": main()