Modern Zig TN5250 + TN3270/TN3270E terminal client (clean-room)
  • Zig 98.7%
  • Objective-C 0.7%
  • Python 0.2%
  • Shell 0.2%
  • Makefile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-08 14:34:47 +00:00
docker build: switch container base Debian -> AlmaLinux 10 (match cluster) 2026-06-17 18:00:09 +02:00
docs feat(cli)!: fold iz into zelnet as subcommands; host verbs exit non-zero on failure 2026-06-29 16:05:18 +02:00
macos feat: free-roam cursor (arrows + csr) + reverse-image bg colors (release 0.9.4) 2026-07-01 10:27:16 +02:00
packaging build(packaging): add nfpm spec for .deb/.rpm 2026-06-19 11:42:20 +02:00
scripts fix(release-prep): portable CHANGELOG surgery (BSD awk rejects multi-line -v values) 2026-06-25 13:35:18 +02:00
src release: v0.10.2 (auto-cadence) 2026-07-08 14:34:46 +00:00
windows feat(gui): Ctrl+D / ⌘D Disconnect accelerator (both backends) 2026-06-25 23:54:46 +02:00
.gitattributes fix(cicd): auto-resolve CHANGELOG land conflicts via .gitattributes merge=union 2026-07-05 12:14:45 +00:00
.gitignore adding .DS_Store to gitignore 2026-06-23 10:13:51 +02:00
AGENTS.md feat(macos): Phase 1 — native AppKit window rendering the screen (-Dgui) 2026-06-18 18:03:26 +02:00
build.zig feat(nvt): pure NVT pass-through engine — host/keyboard byte transforms + expect window 2026-07-02 17:42:12 +02:00
build.zig.zon feat: zelnet foundation — TN5250 + TN3270 protocol core (red/green TDD) 2026-06-17 17:48:35 +02:00
CHANGELOG.md release: v0.10.2 (auto-cadence) 2026-07-08 14:34:46 +00:00
CLAUDE.md feat: zelnet foundation — TN5250 + TN3270 protocol core (red/green TDD) 2026-06-17 17:48:35 +02:00
Makefile refactor!: drop the legacy iz name — remove IZ_* env aliases 2026-06-30 13:35:50 +02:00
README.md feat(tui,main): --nvt plain-telnet mode — interactive pass-through, scripted gate, 5250 auto-fallback 2026-07-02 17:52:41 +02:00
STYLE.md refactor: STYLE.md conformance pass (23 verified findings) 2026-06-18 15:58:50 +02:00

zelnet(1)

A modern, idiomatic-Zig unified IBM terminal client — a clean-room replacement for tn5250 that also drives tn3270 mainframe screens, with the IBM i Host Server data layer (IFS, SQL, CL, data queues, source export) folded into the same single binary.

NAME

zelnet — unified IBM terminal client (TN5250 / TN3270 / plain telnet) and IBM i Host Server client

SYNOPSIS

zelnet [options] <host> [port]
zelnet --tn3270 [--model N] [options] <host> [port]
zelnet --nvt [options] <host> [port]
zelnet <verb> [args ...]
zelnet --demo | --probe <host> [port] | --version

where <verb> is one of auth ifs sql xfer cmd dq pgm src.

DESCRIPTION

zelnet is one statically-linked Zig 0.16 binary with two faces:

  • an interactive full-screen terminal for both IBM screen protocols, and
  • a set of scriptable IBM i Host Server verbs — the ACS data layer (clean-room) — that share the terminal's EBCDIC codec and TLS transport.

It detects its launch context: started from a shell (a controlling TTY) it runs the terminal CLI; double-clicked from Finder or open(1) (no TTY) it opens a native GUI window (macOS AppKit / Windows Win32) when built with -Dgui. --gui / --cli override the heuristic.

TN5250 (IBM i / midrange, AS/400 lineage) — RFC 1205 / 2877 / 4777, GDS 12A0 records, plain (row, col) buffer addressing, field attributes, colour, DBCS (double-byte) via Shift-Out/Shift-In, and concealed password fields. Negotiates IBM-3179-2 (24×80 colour), or IBM-3477-FC (27×132) for a wide display.

TN3270 (IBM z/OS mainframe) — RFC 1576 / 2355, 12/14-bit buffer addressing, and the extended 3270 data stream: colour, extended highlighting (blink / reverse / underscore / intensify), Query Reply, and alternate screen sizes for models 25 (24×80, 32×80, 43×80, 27×132). Negotiates IBM-3278-2-EIBM-3278-5-E. (TN3270E — the RFC 2355 option-40 5-byte header — is staged but not yet negotiated; see CAVEATS.)

NVT (plain telnet, RFC 854) — the floor every telnet session starts on. --nvt is a byte-level pass-through like telnet(1): the enclosing terminal does the rendering while zelnet answers negotiation (remote ECHO, SGA, the local TERM as TERMINAL-TYPE, NAWS window reports; Ctrl-] disconnects). The default 5250 path degrades into NVT automatically when the host negotiates an option no block-mode host uses (WILL ECHO — Cisco/BBS, DO NAWS — Unix telnetd), so zelnet some-switch 23 just works. Block mode is only ever entered when the server explicitly negotiates it, so the detection cannot false-positive.

TLS is implicit ("telnets"): on by default for terminal port 992 and for every Host Server port. The certificate is verified against the OS trust store by default; --self-signed and --no-verify relax that for hosts whose cert is not publicly chained.

OPTIONS

Terminal-client options (the zelnet <host> [port] form):

Option Description
<host> [port] Host to connect to; port defaults to 23.
--tls / --no-tls Force the transport. Default is auto: TLS iff port == 992.
--no-verify Encrypt but skip certificate verification.
--self-signed Accept a valid self-signed certificate (the IBM i default cert).
--tn3270 Speak TN3270 (mainframe). Default is TN5250.
--nvt Speak plain telnet (NVT pass-through): Cisco, BBSes, Unix telnetd.
--model N TN3270 screen model 2/3/4/5 (alternate size + -E type). Default 2.
--demo Render a captured pub400 sign-on transcript offline (no network).
--probe Non-interactive, credential-free connectivity round-trip (diagnostic).
--login Interactive sign-on using ZELNET_USER / ZELNET_PW.
--play <script> Scripted TN5250 session: sign on, then drive and assert panels. With --nvt: a headless expect/send gate (expect <t>; send <t>; type <t>; enter; pause <ms>; dump).
--drive <script> Scripted TN3270 session (implies --tn3270).
--record <file> Write an asciicast of a scripted session (--play / --drive) — the release reel.
--gui / --cli Force the GUI or terminal launch mode.
--version, -v Print the build banner and version, then exit.

In an interactive session: Tab / Shift-Tab move between fields, Enter transmits, F1F24 are the function keys, PgUp / PgDn are Roll, F3 or a lone Esc exits/goes back, Ctrl-C quits. Password fields are non-display (typed characters are concealed).

COMMANDS

The IBM i Host Server verbs. Credentials come from the environment only, never the command line (see ENVIRONMENT). Each verb signs on, runs, prints a result, and exits non-zero on any failure — so they compose into CI gates (zelnet cmd "…" || exit 1).

Command Description
auth [host] Sign on and print the system VRM, server level, and password level.
ifs ls <path> List an IFS directory (the file server).
ifs get <remote> <local> Download a file from the IFS.
ifs put <local> <remote> Upload a file to the IFS.
ifs mkdir <path> / rmdir <path> Create / remove a directory.
ifs rm <path> Delete a file.
ifs mv <src> <dst> Rename / move a path.
ifs stat <path> Show a path's type, size, attributes, and timestamps.
sql "<SELECT ...>" Run a query and print the rows. Non-SELECT runs as EXECUTE IMMEDIATE and reports rows affected.
xfer <table|"SELECT ..."> <file.csv> Data Transfer: export a table or query result to CSV.
cmd "<CL COMMAND>" Run a CL command on the remote-command server and print its messages.
dq write <lib/queue> <data> Write to a data queue.
dq writek <lib/q> <key> <data> Write a keyed entry.
dq read|peek <lib/q> [wait] Read (removing) or peek the next entry; wait seconds to block.
dq readk <lib/q> <EQ|NE|LT|LE|GT|GE> <key> Read by key comparator.
dq clear <lib/q> / attr <lib/q> Clear a queue / show its attributes.
pgm rtvsysval <SYSVAL> Program-call QWCRSVAL for a system value (e.g. QMODEL, QCCSID, QDATE).
src export <lib> [--out <dir>] [--srcfiles QDDSSRC,QRPGLESRC,QCLSRC] [--filter PART*] "Reverse deploy": pull a library's source members into a local Git layout (<out>/<srcfile>/<member>.<type>).

ENVIRONMENT

Variable Meaning
ZELNET_USER / ZELNET_PW Credentials for the Host Server verbs and --login / --play. Never passed on the command line.
ZELNET_HOST Default host for the verbs. Defaults to pub400.com.
ZELNET_ALLOW_PLAINTEXT=1 Required to permit --no-tls on a Host Server verb. Without it, zelnet refuses to send your password substitute and data in the clear.
ZELNET_DUMP=1 Hex-dump every inbound 3270 record (wire debugging).

EXAMPLES

Terminal sessions:

zelnet pub400.com 992          # IBM i over TLS (auto-on for 992), then sign on
zelnet pub400.com 23           # plain telnet
zelnet --self-signed host      # IBM i with its default self-signed cert
zelnet --tn3270 --model 4 host 23   # a 43×80 mainframe screen
zelnet --demo                  # offline render of a captured sign-on
zelnet --probe pub400.com 992  # connectivity check, no credentials

IBM i Host Server (credentials in the environment):

export ZELNET_USER=you ZELNET_PW=secret ZELNET_HOST=pub400.com
zelnet auth                                    # sign on, print V7R5M0 etc.
zelnet ifs ls /home/YOU                         # list a directory
zelnet sql "SELECT SCHEMA_NAME FROM QSYS2.SYSSCHEMAS FETCH FIRST 10 ROWS ONLY"
zelnet cmd "DSPSYSVAL SYSVAL(QDATE)"            # run a CL command
zelnet xfer "QSYS2.SYSSCHEMAS" /tmp/schemas.csv # export to CSV
zelnet pgm rtvsysval QMODEL                      # program-call a system value
zelnet src export MYLIB --out ./MYLIB --filter PART*   # pull source into Git

Scripted / headless (CI):

zelnet --play  signon.zts pub400.com 992   # drive + assert a 5250 panel flow
zelnet --drive logon.z3270 mainframe 23     # drive a 3270 panel flow

EXIT STATUS

0 on success. Any sign-on, connection, or operation failure prints a diagnostic and exits non-zero, so a verb can gate a script or CI step.

TLS AND SECURITY

  • By default TLS verifies the server certificate against the OS trust store (pub400.com's publicly-trusted cert validates cleanly). --self-signed accepts a valid self-signed cert; --no-verify encrypts without authenticating the peer.
  • Implicit TLS runs on terminal port 992 and on the Host Server ports — sign-on 9476, file 9473, database 9471, command 9475, data-queue 9472 (the 84xx counterparts are plaintext).
  • The Host Server verbs refuse --no-tls unless ZELNET_ALLOW_PLAINTEXT=1 is set, so a stray flag can never silently leak your password substitute and data.

BUILDING

Requires Zig 0.16. Thin make wrappers around zig build:

make build      # native CLI binary -> zig-out/bin/zelnet
make run ARGS="pub400.com 992"
make gui        # native GUI (macOS AppKit / Windows Win32, -Dgui)
make app        # assemble zig-out/zelnet.app (macOS bundle)
make ci         # what CI runs: fmt-check + tests (Debug + ReleaseSafe)
make test       # unit tests
make smoke      # live pub400.com sign-on smoke (needs ZELNET_USER / ZELNET_PW)
make dist       # cross-compile every platform -> dist/

Or drive zig build directly: zig build -Doptimize=ReleaseSafe, zig build run -- <host> [port], zig build -Dgui, and zig build check (the CI-parity preflight: zig fmt --check + all unit tests). Cross-compile targets: x86_64-windows, x86_64-linux-musl, aarch64-linux-musl, aarch64-macos, x86_64-macos.

DISTRIBUTION AND RELEASING

src/VERSION is the single source of truth and carries a -dev suffix while developing. The in-cluster pipeline publishes to pkg.sean.farm:

  • Every push is cross-compiled and uploaded per-commit to https://pkg.sean.farm/zelnet/builds/<short-sha>/ (a MANIFEST lists the artifacts) — click-testable release candidates.
  • A release commit drops the -dev suffix (and adds a CHANGELOG.md entry). The pipeline then promotes that commit's already-tested build — no rebuild, so what was tested is what ships — to https://pkg.sean.farm/zelnet/releases/v<x.y.z>/ and the Forgejo releases page, tagging in-cluster.

CAVEATS

  • This is unstable software, written in an unstable language, by unstable people. Opus 4.8 spat most of it out over a handful of days; human eyes have barely been cast upon the code, but it seems to work. For now.
  • I cannot recommend using it for anything important. Will it eat your computer? Maybe.
  • TN3270E is staged, not wired. zelnet currently refuses TN3270E (telnet option 40, the RFC 2355 5-byte data-stream header + LU binding/responses) and speaks basic TN3270 (LU2) with the extended -E data stream. Full-screen TSO/E logon that depends on TN3270E is not yet available.

SEE ALSO

tn5250(1), x3270(1). RFC 1205 / 2877 / 4777 (TN5250), RFC 1576 / 2355 (TN3270 / TN3270E), RFC 854 (Telnet). docs/HOSTSERVER.md — the Host Server wire protocol, with live pub400.com verification notes.