Skip to content

ethernetgateway v0.7.0

Choose a tag to compare

@github-actions github-actions released this 18 Jul 02:14

Added

  • Gateway Shell — a CP/M-inspired file manager over telnet/SSH. A new
    S Gateway Shell item on the File Transfer menu opens an A> command prompt
    that presents the transfer directory as drive A: (flavor A: pure Rust, no
    Z80/.COM emulation). Resident commands DIR/LS, TYPE, DUMP, ERA
    (DEL/RM), REN, COPY (PIP/CP), MOVE (MV), MKDIR (MD),
    RMDIR (RD), CD, PWD, STAT, HELP (?), and EXIT cover full file
    management, including cross-directory copy/move via a /-separated path
    syntax the base CP/M command set can't express, and */? wildcards for
    DIR/ERA/COPY. TYPE/DUMP/DIR paginate with a --More-- prompt, and
    TYPE refuses binary files. Every operand is jailed to the transfer directory
    (validated + canonicalized; ../absolute/symlink escapes are refused); copy/
    move honor the 8 MB transfer cap and the TYPE/DUMP viewers cap reads at
    1 MiB. Works identically over telnet and SSH. Documented in user manual §8.10.
    (Flavor B — a real Z80 CP/M 2.2 emulator — remains deferred.)
  • Third-party license notices and a license-policy gate. THIRD-PARTY-NOTICES.md
    (generated by cargo-about from
    about.toml + about.hbs) reproduces every dependency's copyright notice and
    license text. A new CI licenses job runs
    cargo-deny against the
    GPL-3-compatible allowlist in deny.toml and gates the build (no
    continue-on-error), so a GPL-3-incompatible or unknown-licensed dependency
    can't land silently. README documents the workflow.

Changed

  • Added a full "as is" / no-warranty disclaimer at the top and bottom of
    both README.md and the user manual (usermanual.html), including a note
    that portions of the project were developed with the assistance of AI tools.
  • README.md condensed to a quick-start + feature overview (~1815 → ~190
    lines). The reference material that duplicated the user manual (the annotated
    egateway.conf dump, AT-command / S-register tables, telnet RFC compliance
    matrix, release-verification and systemd walkthroughs, per-distro build
    matrix) now lives only in the manual, with links; the repo-unique hardware
    quick-start, security posture, and license are kept, and the screenshot is
    surfaced near the top.
  • User manual: documented kercpm3's Password: prompt on remote cd.
    CP/M Kermit clients prompt for the optional Kermit CWD password (Protocol
    Manual §6.7 second field) before sending the G C packet; the gateway's
    Kermit server is unauthenticated by design and ignores that field, so the
    directory change always succeeds — press Enter. Also noted the
    recognised-but-unsupported USER command in the Gateway Shell (§8.10).

Fixed

  • Gateway Shell: DIR SUB now lists the subdirectory's contents (like
    DIR SUB/) instead of just showing the <DIR> entry — a wildcard-free
    operand that names an existing directory is listed, matching the DOS/Unix
    expectation. DIR name, DIR *.pat, and DIR file are unchanged.
  • Gateway Shell: name resolution is now case-insensitive. DIR shows names
    uppercased, so a directory stored on disk as z80asm displayed as Z80ASM
    and CD Z80ASM then failed "No such directory." (only the lowercase form
    worked) — and PETSCII terminals swap case on the wire, compounding it.
    CD/TYPE/DUMP/ERA/STAT/REN/COPY/MOVE source operands now match
    an existing name case-insensitively (exact case wins, else the first
    case-insensitive hit) and resolve to the real on-disk name; newly created
    names keep the case as typed. Still fully jailed to the transfer directory.
  • Gateway Shell: the A> prompt after HELP now appears on its own line. The
    help pager's "Press any key" was dismissed with the cursor mid-line, so the
    returning prompt was glued to it (Press any key.A>); the pager now advances
    to a fresh line before returning (harmless for the menu callers, which
    redraw).
  • PETSCII: declining color no longer drops a Commodore terminal to ASCII.
    Color was tracked implicitly by the terminal type, so answering "N" to the
    color prompt forced TerminalType::Ascii — which also discarded PETSCII's
    40-column, case-swapped, ANSI-stripped layout, leaving a C64 caller in an
    80-column ASCII view. Color is now a separate color_enabled flag: declining
    color keeps the detected terminal type (PETSCII stays PETSCII) and simply
    emits plain text. Also makes the SSH/telnet gateway's PETSCII handling correct
    for a no-color C64. Side effect for ANSI terminals: declining color now
    means "no color in the gateway's own menus" rather than "downgrade to ASCII,"
    so remote-host ANSI passed through the SSH/telnet gateway is no longer stripped
    and the onward terminal is advertised as xterm instead of dumb — the
    correct behavior for a terminal that answered the ANSI-color prompt.
  • Serial: a serial-manager thread can no longer panic on a dropped runtime
    across a config restart (round-7 review).
    The detached serial threads
    block_on the tokio runtime, but a SIGHUP restart dropped the runtime
    without joining them; a thread stuck in the synchronous connect_timeout
    (an in-flight ATDT/peer dial to an unresponsive host, up to 60 s) would then
    panic on its next block_on. The dial now connects asynchronously, raced
    against the shutdown/restart flag (aborting within ~100 ms instead of being
    blind for the whole carrier wait), and main bounded-joins the serial
    threads before dropping the runtime. Self-healing before; airtight now.
  • Web browser: a hostile page can no longer soft-DoS the render thread with
    form-label lookups (round-6 F1).
    Each form field with an id but no
    placeholder/aria-label/title triggered a full recursive walk of the form
    subtree looking for <label for="id"> — O(fields × subtree), so a page of
    tens of thousands of bare <input id=…> (under the 1 MB body cap) cost
    quadratic CPU on a shared spawn_blocking thread with no render time budget.
    Labels are now collected in a single O(subtree) pass into an id→label map;
    per-field lookup is O(1).
  • Weather: a malformed forecast timestamp no longer panics the fetch
    (round-6).
    The MET.no forecast parse sliced the first 10 bytes of the
    time field guarded by byte length only; a timestamp with a multibyte char
    in the first 10 bytes would panic on a mid-character boundary. It now uses a
    char-boundary-safe accessor and skips a malformed entry. (Was contained to a
    single weather fetch by spawn_blocking, never a process crash.)
  • Serial: dialing a console-mode port relayed to the master no longer wedges
    the caller's serial thread (round-5 review).
    On a slave gateway with
    allow_peer_dial on, ATD <ConsolePort>@<local-ip> reached a local console
    bridge that nothing services (the console port runs the master-registration
    loop, not the local bridge), so the caller's thread blocked forever on the
    bridge oneshot — unrecoverable short of a full restart. connect_local_peer
    now fails that dial fast with NO CARRIER (mirroring the Serial Gateway
    picker's exclusion), and the console-bridge request is additionally raced
    against a shutdown/restart poll so no path can pin the thread.
  • Relay: IPv6 onward-dial targets are handled correctly (F1). The onward-
    dial path split/rebuilt host:port with a bare rsplit_once(':'), leaving
    brackets on an IPv6 host so connect failed. A shared split_dial_host_port
    now parses [2001:db8::1]:6400 into a bare literal, the slave brackets IPv6
    on the wire, and both halves agree (unbracketed IPv6 is rejected as
    ambiguous). IPv4/hostname dialing is unchanged.
  • Telnet: a session-slot / broadcast-writer leak on a panicking session is
    now prevented (F3).
    Slot release and writer de-registration ran only after
    session.run() returned, so a future reachable panic would leak a
    max_sessions slot and grow the broadcast list unbounded. A RAII backstop
    (SessionSlotGuard) now reclaims both on unwind; the normal path defuses it
    after the graceful async cleanup. (No panic is reachable today — this is
    defensive hardening consistent with the SSH/relay Drop guards.)
  • Kermit receive: windowed receiver now ACKs buffered out-of-order packets
    (selective repeat, spec §5.5) (K1).
    Previously it buffered a correctly-
    received future packet but only NAKed the missing expected_seq — once per
    future — never ACKing the good packet; the windowed sender counted each
    duplicate NAK as a retry and re-sent packets it didn't need to. The receiver
    now ACKs each future by its own seq and NAKs the gap once, matching the spec
    and removing the redundant retransmissions (and the retry-budget pressure a
    reordering link could put on a large window). Live C-Kermit 10.0 sliding-
    window interop unchanged.
  • Punter receive: a premature-retransmit duplicate no longer corrupts the
    file (P1/P2).
    On a slow/jittery link a data block whose first byte was
    delayed past the byte-wait could trigger an early S/B resend; the delayed
    block and a re-sent copy would both arrive, and the receiver — which ignored
    the block index for sequencing — appended the interior block twice,
    returning a silently one-block-too-long file. The receiver now dedups on the
    checksum-protected NUMPOS block index (dropping a block whose index did not
    advance), bounded so a peer stuck re-sending one block still gives up. Verified
    against the live CCGMS reference (both directions) — the dedup never triggers
    on conforming traffic.
  • Punter: a mid-IAC-sequence timeout in a handshake window is now recoverable
    (P3).
    accept_code treats the tnio IAC-timeout (N4) as a soft "no code
    this round" re-probe — matching read_block — instead of aborting the whole
    transfer; other read errors still abort.
  • Serial: a boot-time thread-spawn failure no longer panics the whole
    process (N5).
    start_serial logs the failure and continues, so the rest
    of the gateway (telnet/SSH/web and the other serial port) still comes up;
    only the affected port is disabled.
  • Telnet I/O: a truncated IAC sequence can no longer wedge a reader (N4).
    Bytes read inside an already-started IAC sequence (the command byte, and a
    WILL/WONT/DO/DONT option byte) are now bounded by a 5 s timeout — matching
    the existing SB-drain bound — so a peer that sends a lone 0xFF and stalls
    can't block read_exact forever. The first-byte wait is still caller-timed.
  • ZMODEM send: the post-ZEOF ZRPOS recovery reuses the main data-send path
    (Z6).
    The recovery previously duplicated the ZDATA/subpacket loop inline
    (a drift risk with weaker ACK handling); it now calls the same
    send_zdata_run helper as the initial data phase.
  • ZMODEM send: ZFILE now advertises binary conversion (Z3). The ZFILE ZF0
    byte carries ZCBIN instead of 0, so a text-defaulting receiver won't apply
    newline translation to a binary payload.
  • ZMODEM: slow-link timeouts are no longer capped by hardcoded literals
    (Z5).
    The between-files header wait now uses the configured
    zmodem_frame_timeout (was a fixed 10 s), and the post-ZEOF ZRINIT wait
    keeps its 15 s fsync floor but rises to a tuned-up zmodem_frame_timeout.
  • XMODEM receive: the 8 MB file cap is enforced exactly (X2). The size
    check now runs before appending each block, so the buffer never exceeds
    MAX_FILE_SIZE even transiently (previously a file could grow one block
    past the limit before the top-of-loop check fired). Exactly 8 MB is still
    accepted.
  • Web browser: the DOM text-extraction dependency is pinned against silent
    breakage (A2).
    Title/form-text extraction parses html2text's debug DOM
    rendering (html2text 0.14 exposes no Text-node walk); a canary test now
    guards that format so a dependency bump fails loudly in CI instead of
    silently returning empty titles/labels.
  • XMODEM receive: auto-detect no longer stalls 60 s against a strict
    lock-step checksum-only sender (X1).
    On the first block, when the session
    is in CRC mode but the sender emits a single 1-byte checksum trailer and then
    waits for our ACK/NAK (vintage Christensen 1977 / CP/M MODEM7 / C64 BBS
    uploaders that ignore our C), the CRC low-byte read is now gated behind a
    short grace window: if no second trailer byte arrives, the receiver falls
    back to 1-byte-checksum validation and locks to checksum mode instead of
    blocking until the full block-body timeout. A genuine CRC sender's low byte
    arrives back-to-back and is unaffected; after the first block the mode is
    locked and the read blocks unconditionally. The symmetric checksum-mode
    auto-detect branch (the extra CRC-probe read on a first-block checksum
    mismatch) is gated the same way, so a lock-step checksum sender with a
    corrupt first block is NAKed promptly rather than after 60 s.
  • ZMODEM receive: the data-phase retry counter is bounded consistently.
    nak_or_abort now tolerates max_retries consecutive errors (>), matching
    the ZFILE-subpacket and XMODEM counters and its own "bounded by max_retries"
    contract, instead of aborting one retry early (>=).
  • ZMODEM receive: a corrupt ZFILE info subpacket no longer aborts the whole
    batch (Z1).
    The filename/size subpacket is now read with the same
    ZNAK/retry discipline as the data phase — per Forsberg §7 the receiver ZNAKs
    and the sender retransmits the ZFILE frame — so a single bit-flip or
    truncation in the metadata is recovered instead of killing the transfer.
    Bounded by zmodem_max_retries, so a permanently broken link still cancels.
  • ZMODEM receive: the sender's "OO" over-and-out trailer is now drained
    (Z2).
    After replying to ZFIN the receiver consumes the two O bytes the
    sender emits per §8.4; previously they leaked into the terminal session that
    resumed after the transfer as spurious OO input. Best-effort with a short
    timeout — a peer that omits OO is unaffected.
  • SSH: reject auth when the configured username or password is empty (N2).
    Because constant_time_eq(b"", b"") is true and SSH has no
    unauthenticated mode, an operator who blanked the password would otherwise
    turn the SSH port into an open shell bridge. Auth is now refused outright
    when either stored credential is empty.