You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.