Overview

Kermit is a robust file-transfer protocol designed in 1981 by Frank da Cruz at Columbia University. Unlike XMODEM/YMODEM/ZMODEM, Kermit was built for environments where bytes can be lost, mangled, or stripped to 7 bits — old serial links, packet-switched networks, and bridged terminal hardware. Ethernet Gateway implements a spec-complete Kermit on both directions: as a client connecting to a remote Kermit server, and as a server idling waiting for commands from a peer.

The implementation follows Frank da Cruz, Kermit, A File Transfer Protocol (Digital Press, 1987) plus the long-packet, sliding-window, and streaming extensions used by C-Kermit. Real C-Kermit 10.0 interop is verified by the test suite for both upload (send) and download (get) directions.

When to use Kermit
7-bit 7-bit links Old serial connections, telnet bridges that strip the high bit, mainframe links — XMODEM family doesn't survive these without a layer of escaping; Kermit was designed for them.
flaky Lossy connections Per-packet ACK + sliding window + selective-repeat retransmit recovers cleanly from drops; resume-partial picks up where a broken link left off.
cross Cross-platform peers Kermit is on every retro platform that mattered: VMS, MVS, CP/M, MS-DOS, every UNIX. C-Kermit, G-Kermit, Kermit-95, Kermit-86, MS-Kermit, E-Kermit are all interoperable.
batch Multi-file batches A single S/B exchange carries many files; the spec is self-describing (filename, mtime, mode all carried in the A-packet metadata).
Capabilities advertised: sliding windows (up to 31), streaming (TCP/SSH only — no per-packet ACKs), long packets (up to 9024 bytes), attribute packets (file size, mtime, mode, encoding, record format, creator, account, …), CRC-16 block check, repeat compression, RESEND, locking shifts.

Server Mode

Three entry paths drop a session into Kermit server mode:

  • Telnet / SSH menu — from the File Transfer menu press K. Honors authentication, the network gating allowlist, and the per-session sandbox.
  • Standalone TCP listener — with kermit_server_enabled = true, the gateway binds a dedicated socket on kermit_server_port (default 2424). Every accepted connection enters protocol mode immediately — no menu, no auth gate. Drive it with kermit -j host:2424.
  • Serial ATDT KERMIT — with allow_atdt_kermit = true, dialing ATDT KERMIT from the modem emulator hands the serial caller straight to the dispatcher. Aliases: kermit, kermit-server, kermit server.

The bottom two paths bypass security_enabled by design — they exist so vintage receivers that can't paint a menu still work. Both are off by default and have explicit GUI / telnet-menu warnings before they enable.

However the session is launched, the server then idles waiting for commands from the Kermit client. The peer drives the entire session by sending Kermit protocol commands; the server processes each one and returns to idle until the peer sends finish, bye, or disconnects.

Entering server mode

Files sent to the server are saved into transfer_dir (or its current sub-directory if the peer changed it via remote cwd). The server returns a per-file ✓/× summary when the session ends.

What the server accepts
TypeFrom peerServer response
SSend-Init starting an uploadY-ACK with our capabilities, then drives the receive flow (F/A/D…/Z/B).
RReceive-request: peer wants a file from usY-ACK to R, then sends S+F+A+D…+Z+B for the requested file.
IInit: peer asks us to advertise capabilitiesY-ACK with a fresh Send-Init payload listing our caps.
GGeneric command (F/L/B/I/X/C/D/$/K/H/?/E/R/T/m/d)See the G subcommand reference.
CHost command (run a shell command)Refused with E-packet "Host commands disabled". Always.
EError packet from peerLogged; session ends silently (per spec, E is fatal both ways).
BEOT — end of transmissionACK; session ends cleanly.
Host commands stay refused: C packets are a remote-code-execution primitive by design. The server emits an E-packet "Host commands disabled" and keeps idling. There is no configuration switch to enable them — actually executing peer-supplied shell commands is out of scope and unsafe regardless of who you trust.

Peer Commands

These are the commands you type at the C-Kermit prompt (or any spec-compatible Kermit client) once connected to the gateway in server mode. The wire-protocol packet type the client emits is shown in the second column — useful when reading verbose logs or comparing with the packet-type reference.

CommandWireWhat it does
send <file>
aka put
S Upload a file from your local system into the gateway's current effective directory (transfer_dir + any cwd you set this session). Wildcards and multi-file batches work — send *.bas sends every match in one S/B exchange.
get <file> R Download a file from the gateway's current effective directory to your local system. The filename is path-traversal-validated server-side; an unsafe name returns an E-packet and the server stays idle for the next command.
remote dir G D List the gateway's current effective directory, one entry per line as name <TAB> size|<dir>. Hidden files (leading dot) are filtered out. Long listings are paginated automatically across multiple X-packets.
remote cwd <subdir>
aka remote cd
G C Change the gateway's working sub-directory for the rest of this session. Resolved relative to transfer_dir. Special cases:
  • remote cd with no argument resets back to the transfer_dir root.
  • remote cdup pops one component (no-op at the root). This is the reliable form for "go up." remote cd .. works only when the C-Kermit client passes .. straight through to the wire; some C-Kermit builds canonicalize .. against the local filesystem first, in which case the server never sees it. cdup always sends the literal .. per ckuus7.c:7762.
  • A target that doesn't exist on disk is refused with an E-packet, so a typo'd remote cd asembly fails fast instead of silently dropping uploads into a bogus path.
Path-traversal (.. as a component anywhere except as the bare CDUP argument, leading dot, backslash, NUL) is refused.
remote space G U Report free disk space, in bytes, at the current effective directory. Returns unknown on platforms without statvfs. The wire letter is U (Frank da Cruz Generic Command Letters §6 Table 6-2: "disk Usage") — that's what real C-Kermit emits via setgen('U', …). The gateway also accepts the legacy $ letter for backward compatibility with our own client helper.
remote kermit <text> K Ask the gateway to identify itself. Replies with Ethernet Gateway Kermit 0.6.1 regardless of the text you supply, delivered via the same X+Z inverse-transfer pattern as remote dir.

Argument is required. If you type remote kermit with no text after it, C-Kermit silently does nothing and never sends a packet — that's the client's behavior, not the server's. Type any non-empty argument to actually invoke the command. Conventional choices:

C-Kermit> remote kermit version
Ethernet Gateway Kermit 0.6.1
C-Kermit> remote kermit info
Ethernet Gateway Kermit 0.6.1

Wire shape note: this is a top-level K-packet (the user's command rides in the payload), not a G-K subcommand. C-Kermit builds the packet via scmd('K', cmarg); G-K is reserved for remote copy in the spec. A separate G K path is preserved internally for our own client helpers but real Kermit clients never emit it.

remote help G H / G ? Display the gateway's supported-subcommand list. Both H and ? are accepted on the wire so older clients work too.
remote delete <file>
aka remote era, rdel
G E Delete a file in the gateway's current effective directory. Filename is path-traversal-validated; missing or unsafe names return an E-packet. Wildcards are not expanded — operate on one filename per call.
remote rename <old> <new>
aka rrename
G R Rename a file in place. Both names are field-encoded per spec §6.7 and validated separately; if the destination already exists the server refuses with an E-packet rather than silently clobbering it.
remote type <file>
aka rtype
G T Display a file's contents on your terminal. Server delivers the file via the same X+Z inverse-transfer pattern as remote dir. Intended for text files — sending a binary will spew control bytes to your terminal, just like real Kermit.
remote mkdir <dir> G m Create an empty subdirectory under the current effective directory. Wire letter is lowercase m per C-Kermit's setgen('m', …) — distinct from M (which the spec reserves for remote message). Single-component name only; nested-path creation is refused.
remote rmdir <dir> G d Remove an empty directory. Lowercase d wire letter — distinct from D (DIRectory listing). Non-empty directories return an E-packet refusal; the operator must remote delete the contents first.
finish G F End the protocol session and return the gateway to the File Transfer menu. Your client stays connected at the telnet/SSH layer — most polite end-of-session.
logout G L End the protocol session. Semantically equivalent to finish at the wire level — some hosts treat it as "log out the user" but the gateway treats both the same.
bye G B End the protocol session. Some clients additionally drop the underlying connection after sending bye; the gateway-side effect is identical to finish.
What about copy, host, set, who, print, message, query, status? These don't fit a file-transfer gateway: host is a remote-shell primitive (security risk), who / set / login assume a multi-user host the gateway doesn't model, and copy overloads the K wire letter with KERMIT-identity (the dispatch distinction needs special-casing on argument count, not landed yet). The server refuses every unsupported subcommand with an E-packet per spec §6, so your client surfaces a clean "command not supported" error rather than the silent-ACK no-op that pre-2026-05 builds emitted.
Path safety: Both get filenames and remote cd sub-directory arguments are validated before any disk I/O. Any name containing .., leading dots, separator characters, or NUL bytes is refused with an E-packet, and the server keeps idling for the next command (per spec §6.7).
Filename arrives uppercased? That's C-Kermit, not us. By default C-Kermit ships filenames in common form — uppercased ASCII, with characters outside the portable set rewritten to X, and on some legacy clients truncated to 8.3. Frank da Cruz adopted the convention so a file named on a VMS/MVS/Unix box would still land safely on a CP/M target. The gateway saves whatever the F-packet says, so put hello.txt from a default C-Kermit becomes HELLO.TXT in transfer_dir. To keep the original case and full length, run this once in your C-Kermit before the transfer:
C-Kermit> set file names literal
That switches both the sent F-packet (your put) and the received F-packet (incoming get) to pass the name through unmodified. You can pin it permanently in ~/.kermrc. Kermit-95 has the same setting under Settings → File Names → Literal; G-Kermit and E-Kermit have no toggle and always send the name unmodified.

Packet Types

Every byte on a Kermit connection belongs to a packet. All packets share the same frame: a MARK byte (SOH, 0x01), length and sequence fields, a one-character TYPE, the payload, a block-check (checksum or CRC), and an end-of-line terminator. The TYPE byte determines what role the packet plays — whether it's negotiating capabilities, carrying file content, acknowledging a previous packet, or signalling an error. Below is what each TYPE letter means and why it exists.

MARK  LEN  SEQ  TYPE  DATA…  CHECK  EOL
 \x01  N+3   s    T   payload   c1[c2c3]  \r

LEN and SEQ are tochar()-encoded (value+0x20) so every framing byte stays in the printable ASCII range — the protocol survives 7-bit, control-stripping links by construction. SEQ is a 6-bit counter that wraps mod-64 across every packet in the conversation, including ACKs and NAKs.

S — Send-Init

The opening packet of every transfer. Its payload is the sender's capability proposal: maximum payload length, packet timeout, padding requirements, end-of-line terminator, control-quote character, 8-bit-quote character, block-check type, repeat-count prefix, plus a CAPAS bitmap advertising long packets, sliding windows, attribute packets, locking shifts, and streaming. The receiver replies with its own Send-Init in the Y-ACK; the intersection of the two proposals governs the rest of the transfer.

F — File header

Names the file the sender is about to deliver. The payload is the filename, plain (un-quoted) — most implementations don't escape it because filenames rarely contain QCTL or control bytes. In a multi-file batch, each file gets its own F between the initial S and the closing B; the receiver opens a new output file each time.

A — Attribute

Carries metadata about the file that just arrived in F: size, modification time, mode bits, encoding, record format, system-id, charset, creator, account, and ~10 other typed sub-fields (each introduced by a single-byte tag — ! for length, # for date, + for mode, " for binary-vs-text, and so on). Optional but useful: it's how mtime and mode survive the transfer end-to-end. The receiver's Y-ACK to the A can also carry disposition='R' + an offset to ask the sender to resume from a partial file already on disk.

D — Data

One chunk of file content, sized up to the negotiated MAXL. Control bytes are quoted with QCTL, optionally 8-bit-quoted with QBIN to survive 7-bit links, and optionally repeat-compressed (a run of N identical bytes encodes as ~ count byte). One or more D-packets sit between the A (or F if no A was sent) and the closing Z. In streaming mode the per-D ACK is suppressed; otherwise every D gets a Y-ACK before the next is sent.

Z — EOF

Marks the end of one file. Empty payload on a normal finish; D in the payload signals "discard" — the sender aborted mid-file and the receiver should drop what it has rather than save a truncated file. After Z the sender either starts the next file with a new F (still inside the same session) or closes the session with B.

B — EOT (Break)

Ends the entire send session. Empty payload. The receiver Y-ACKs and both sides return to idle. In server-mode dispatch, B is also accepted as a standalone "end of session" signal — the same effect as a G F finish.

Y — ACK

Affirmative acknowledgement. The receiver sends a Y for every non-streaming packet from the sender, echoing the same SEQ. Most Y-packets have an empty payload, but two are special: the Y to the initial S carries the receiver's counter Send-Init (its own capabilities), and the Y to A can carry a disposition + offset to drive RESEND.

N — NAK

Negative acknowledgement: the receiver got a corrupt packet, an out-of-order packet, or timed out waiting for the next expected SEQ. The sender retransmits the packet at the NAK'd SEQ. With sliding windows, NAK drives selective-repeat — only the missing packet is re-sent, not everything after it.

E — Error

Fatal abort. Payload is a human-readable error message, control-quoted. Sent in either direction when something irrecoverable happens (filename refused for path-traversal, file too large, host commands disabled, peer abort). The recipient of an E does not reply — per spec, ACKing an E risks an error loop. The session ends silently after the receiver logs the message.

R — Receive-Init

Client-side download request. Payload is the filename the client wants from the server. The server validates the filename, looks the file up under the current transfer directory, and (if it exists) plays the role of sender for the rest of the exchange — its S follows the R in the same monotonic SEQ stream rather than restarting at zero. R is what C-Kermit's get command emits.

G — Generic command

Server-mode command dispatch. Payload is a single action byte (F/L/B end the session, C changes directory, D lists the directory, $ reports free space, K identifies the server, H or ? shows help) optionally followed by an argument. Short replies (CWD, finish) are a single Y-ACK; long replies (DIR, HELP) drive an inverse file transfer back to the peer — see the G-subcommand reference for the per-letter semantics.

I — Init

Mid-session re-initialisation. Same payload shape as S, but it doesn't start a transfer — the peer just wants a fresh Send-Init to refresh its picture of our capabilities. C-Kermit emits an I before each server-mode command. The server replies with its current caps in the Y-ACK and keeps idling.

X — Text header

Same role as F, but flags the body as text-for-display rather than save-to-disk (Frank da Cruz §5.3). Used when the server is answering a generic-command text query (remote dir, remote space, remote help): the body comes back as a full inverse file transfer (S → X → D…D → Z → B), and a spec-aware peer prints it on screen instead of writing it to a file.

C — Host command

A request for the server to execute an arbitrary shell command. This is a remote-code-execution primitive by design, and the gateway always refuses it with an E-packet "Host commands disabled". No configuration switch enables host commands — actually running peer-supplied shell commands is out of scope and unsafe regardless of who you trust.

T — Timeout

Reserved by the spec for "I timed out reading the line" — a way for the receiver to admit the line went quiet without speculating about why. Never appears on the wire in this implementation; NAK serves the same role and is what every modern peer expects.

Q — Reserved

Reserved by the spec for future use. Not emitted by the gateway and not handled in dispatch — receiving a Q falls into the unknown-type fallback (NAK back at the expected SEQ to drive a retransmit).

Putting it together: A normal single-file upload looks like S → Y → F → Y → A → Y → D → Y → … → D → Y → Z → Y → B → Y. The same shape repeats per file in a multi-file batch — F-headed sub-transfers chained between the opening S and the closing B. A download is the same wire sequence preceded by an R from the client. A server-mode session is a loop of these exchanges interleaved with G commands, terminated by G F / B or peer disconnect.

G Subcommand Reference

Generic-command (G) packets carry a single action byte followed by an optional argument. The server recognises every spec-defined subcommand and replies appropriately.

LetterNameReply shapeNotes
FFinishACK, exitEnd the protocol session. Most polite end-of-session signal.
LLogoutACK, exitSame as F at the protocol level.
BBYEACK, exitSame as F. Some clients use BYE to also signal "drop the connection".
CCWDACK on success, E on failureField-encoded argument is a relative subdir. Special cases: empty argument resets to transfer_dir root; bare .. pops one component (cdup, no-op at root). Other path-traversal forms (foo/.., leading dot, backslash, NUL) are refused. Targets that don't exist on disk are refused so typo'd cd fails fast.
DDirectoryACK + X+ + ZReturns one entry per line: name <TAB> size|<dir>, sorted, hidden files skipped. Paginated across multiple X-packets when needed.
$SPACEACK + X + ZReturns free-bytes count as a decimal string, or unknown on platforms without statvfs.
KKERMITACK + X + ZReturns server identity + version (Ethernet Gateway Kermit 0.6.1).
H / ?HelpACK + X+ + ZReturns the list of supported subcommands. Both H and ? are accepted for compatibility.
EErase / DeleteACK on success, E on failureField-encoded filename per spec §6.7. Path-traversal rejected; missing files return E-packet (not silent ACK). One filename per call — no wildcard expansion.
RRenameACK on success, E on failureTwo field-encoded names back-to-back: source then destination. Server refuses if the destination already exists, to avoid silent clobber.
TTypeACK + X+ + ZField-encoded filename. Server delivers file contents via inverse transfer. Intended for text — sending a binary will spew control bytes to the client's terminal.
mMkdirACK on success, E on failureLowercase per C-Kermit ckuus7.c:8139. Field-encoded directory name; single component only. E-packet on collision (existing dir) or invalid name.
dRmdirACK on success, E on failureLowercase — distinct from D (DIR). Removes only empty directories per std::fs::remove_dir semantics; non-empty returns E-packet so the operator clears contents intentionally.
ILogoutACK, exitWhat C-Kermit's remote logout sends (setgen('I', …)). Same effect as F/L/B at the gateway. Distinct from the I-packet (TYPE_INIT) which is a different packet type.
XExitACK, exitWhat C-Kermit's remote exit sends. Same effect as F/L/B/I at the gateway.
other(unsupported)E-packetPer spec §6, unsupported subcommands return an explicit refusal so the peer's UI surfaces a real error. Pre-2026-05 builds silently ACKed and looked like phantom successes.

Where the reply is ACK + X+ + Z, the body of the response is delivered in one or more X-packets followed by a single Z to terminate. Long responses (e.g. a directory with many files) are paginated to fit within the classic 94-byte MAXL so the response works against strict-spec peers that haven't negotiated long packets.


Capabilities & Negotiation

Both peers exchange a Send-Init (S) packet at the start of a transfer. Each side advertises what it can do; the intersection is what the actual transfer uses. Below are the capabilities the gateway advertises and what each one buys you.

CapabilityWhat it doesDefault
Long packets Up to 9024-byte payloads instead of the classic 94-byte cap. Massive throughput win for clean links. On
Sliding windows Up to 31 outstanding packets in flight. Hides round-trip latency; peer NAKs trigger selective-repeat retransmit per spec §5.5. On (window=4)
Streaming Sender skips per-packet ACKs entirely on reliable links (TCP/SSH). Peer only sends NAK on error. On
Attribute packets (A) Carries file size, mtime, mode, encoding, record format, creator ID, and ~10 other typed metadata fields. On
Repeat compression Runs of identical bytes are encoded as ~ count byte. Cheap RLE for spaced-out output. On
CRC-16 block check 16-bit CRC per packet (polynomial 0x8408, reflected CCITT) instead of the classic 6-bit checksum. Type 3
RESEND Resume a partial upload from where the previous attempt left off — receiver advertises disposition='R' + offset in the A-packet ACK. Off (opt-in)
Locking shifts SO/SI region markers for 8-bit data over 7-bit links instead of QBIN's per-byte prefix. Spec §3.4.5; rarely used by modern peers. Off (opt-in)
RESEND opt-in: Resume support is off by default because a peer that doesn't honor disposition='R' would re-send a file from byte 0 while the receiver pre-loaded partial bytes — corrupting the result. Enable kermit_resume_partial = true in egateway.conf only when both ends are known compatible.

Configuration Keys

Set in egateway.conf; changes take effect on the next Kermit session.

KeyDefaultEffect
kermit_negotiation_timeout300Seconds for the Send-Init handshake.
kermit_packet_timeout10Per-packet read deadline after Send-Init.
kermit_idle_timeout300Seconds the gateway's server-mode dispatch waits between commands from the peer before sending an "idle timeout" E-packet and disconnecting. Set to 0 to disable — server idles indefinitely.
kermit_max_retries5Maximum NAK / timeout retries per packet before giving up.
kermit_max_packet_length4096MAXL we advertise (10..=9024). Higher = more throughput, more retransmit cost on a flaky line.
kermit_window_size4Sliding-window depth (1..=31). 1 = stop-and-wait.
kermit_block_check_type31 = 6-bit checksum, 2 = 12-bit, 3 = CRC-16/KERMIT.
kermit_long_packetstrueAdvertise long-packets capability.
kermit_sliding_windowstrueAdvertise sliding-window capability.
kermit_streamingtrueAdvertise streaming-Kermit (no per-packet ACKs). Big speed win on TCP/SSH.
kermit_attribute_packetstrueAdvertise A-packet (file metadata) support.
kermit_repeat_compressiontrueUse REPT compression for runs of identical bytes.
kermit_8bit_quoteautoauto (only when peer asks), on, or off.
kermit_resume_partialfalseResume partial uploads via disposition='R' in the A-packet ACK.
kermit_resume_max_age_hours168Ignore on-disk partials older than this. 168 = one week.
kermit_locking_shiftsfalseAdvertise SO/SI region-shift quoting for 8-bit transit on 7-bit links.
kermit_server_enabledfalseBind a dedicated TCP listener on kermit_server_port. Every accepted connection drops straight into Kermit server mode — no telnet menu, no auth gate. Bypasses security_enabled; off by default.
kermit_server_port2424TCP port for the standalone Kermit-server listener. Used only when kermit_server_enabled = true.
allow_atdt_kermitfalsePermit the serial modem emulator to recognise ATDT KERMIT (alt: kermit, kermit-server, kermit server) as a dial target that drops the caller straight into the Kermit dispatcher. Bypasses security_enabled; off by default.

Examples
Pull a file from the gateway with C-Kermit
$ kermit -B -j 192.168.1.160:2323 -i -q -g report.txt -a /tmp/report.txt

-B batch mode, -j host:port opens a telnet-protocol TCP connection, -i binary (image) transfer, -q quiet, -g remote-name get the named file, -a local-name save it locally as a different name. The gateway must be in Kermit Server Mode (File Transfer menu → K) when ckermit dials in.

Upload a file to the gateway
$ kermit -B -j 192.168.1.160:2323 -i -q -s ./localfile.bin

By default the file lands as LOCALFILE.BIN on the gateway — C-Kermit uppercases filenames in the F-packet for cross-platform safety. To preserve the exact case and avoid truncation on legacy clients, send a -C one-shot command first to flip the file-names mode to literal:

$ kermit -B -j 192.168.1.160:2323 -i -q -C "set file names literal, send ./localfile.bin"
Interactive session with multiple commands
$ kermit
C-Kermit> set host 192.168.1.160:2323 /telnet
C-Kermit> set file names literal
C-Kermit> remote kermit version
Ethernet Gateway Kermit 0.6.1
C-Kermit> remote dir
alpha.bin    1024
beta.bin     2048
docs         <dir>
C-Kermit> remote cd docs
C-Kermit> put hello.txt
C-Kermit> get readme.txt
C-Kermit> finish
C-Kermit> exit

The set file names literal line keeps hello.txt from being uppercased to HELLO.TXT in transfer_dir. Drop it if you want the default common-form rewriting. Each put commits to disk as soon as the gateway ACKs your B-packet, so files appear in transfer_dir immediately — finish is just for closing the session politely.

Interactive C-Kermit needs a controlling tty for some commands — if you're scripting, keep using -g / -s action flags or wrap with script -qc to provide a pseudo-terminal.


Spec References
  • Frank da Cruz, Kermit, A File Transfer Protocol, Digital Press, 1987 — the canonical reference. The base protocol (Send-Init, F/A/D/Z/B), G-packet semantics, and the attribute-packet sub-attribute table all come from here.
  • Frank da Cruz & Christine Gianone, Using C-Kermit, Digital Press, 1997 — server-mode operation, the long-packet and sliding-window extensions, streaming Kermit.
  • kermitproject.org — ongoing reference site for the modern Kermit family. C-Kermit source is available there.
Test coverage: The Kermit module ships with 920+ unit tests covering every spec-defined packet type, capability negotiation, all 7 typed A-packet sub-attributes, RESEND edge cases, locking-shift round-trips, server-mode dispatch, client-mode primitives, proptest fuzzers on adversarial wire bytes, and real C-Kermit 10.0 subprocess interop for both send and get.