#!/usr/bin/env python3 """Serve VT1200 font files over TFTP. The terminal's "TFTP Font Path" is ``:``; it requests that exact filename over TFTP and (if it parses) loads it as a font. This serves several plausible names so we catch whatever it asks for. sudo python3 -u harness/serve.py [host] [port] """ import os import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) sys.path.insert(0, os.path.join(ROOT, "tools")) import fontgen FONTS = os.path.join(ROOT, "reference", "fonts") def load(name): with open(os.path.join(FONTS, name), "rb") as fh: return fh.read() def main(): baseline = load("baseline.pcf") evil = load("evil-real.pcf") probe = load("probe-uniq.pcf") probe_snf = load("probe.snf") probe_dec = load("probe-dec.snf") probe_dec2 = load("probe-dec2.snf") real = load("system-10x20.pcf") fonts_dir = load("fonts.dir") files = {} # The font path value is :. The terminal fetches # "/fonts.dir" (the directory index), then the font files it names. for d in ("69", "font", "fonts", "misc", "X11"): files[f"{d}/fonts.dir"] = fonts_dir files[f"{d}/baseline.pcf"] = baseline files[f"{d}/v1200a.pcf"] = baseline files[f"{d}/evil.pcf"] = evil # also serve the plain names, in case it asks for those first for n in ("69", "fonts.dir", "font", "fonts", "ROM", "rom", "real.pcf"): files[n] = real files["baseline.pcf"] = baseline files["evil.pcf"] = evil if os.path.exists(os.path.join(FONTS, "fonts.dir")): files["fonts.dir"] = load("fonts.dir") host = sys.argv[1] if len(sys.argv) > 1 else "100.103.255.254" port = int(sys.argv[2]) if len(sys.argv) > 2 else 69 def resolve(name): # The terminal first fetches the bare path, then "/fonts.dir". # Accept ANY name: the index for fonts.dir, the payload otherwise. if name == "fonts.dir" or name.endswith("/fonts.dir"): return fonts_dir if name.endswith(".snf"): if "dec2" in name: return probe_dec2 return probe_dec if "dec" in name else probe_snf if name.endswith((".pcf", ".DECW$FONT")): if "evil" in name: return evil if "probe" in name: return probe return baseline return baseline # bare directory name: give it data, not an error srv = fontgen.TFTPServer((host, port), files, timeout=60.0, retries=10, resolver=resolve).start() print(f"serving {len(files)} names on {srv.address}", flush=True) print(" names: " + ", ".join(sorted(files)), flush=True) try: while True: time.sleep(0.5) while srv.log: ts, name, addr = srv.log.pop(0) stamp = time.strftime("%H:%M:%S", time.localtime(ts)) print(f"{stamp} {addr[0]}:{addr[1]} requested {name!r}", flush=True) except KeyboardInterrupt: pass finally: srv.stop() if __name__ == "__main__": main()