Skip to content

Commit 37a7226

Browse files
authored
2.5.0 (jquast#122)
- **change**: `telnetlib3-client` now defaults to raw terminal mode (no line buffering, no local echo), which is correct for most servers. Use `--line-mode` to restore line-buffered local-echo behavior. - **change**: `telnetlib3-server --pty-exec` now defaults to raw PTY mode. Use `--line-mode` to restore cooked PTY mode with echo. - **change**: `connect_minwait` default reduced to 0 across `BaseClient`, `open_connection()`, and `telnetlib3-client`. Negotiation continues asynchronously. Use `--connect-minwait` to restore a delay if needed, or use `TelnetWriter.wait_for()` in server or client shells to await a specific negotiation state. - **new**: Color, keyboard input translation and `--encoding` support for ATASCII (ATARI ASCII) and PETSCII (Commodore ASCII). - **new**: SyncTERM/CTerm font selection sequence detection (`CSI Ps1 ; Ps2 SP D`). Both `telnetlib3-fingerprint` and `telnetlib3-client` detect font switching and auto-switch encoding to the matching codec (e.g. font 36 = ATASCII, 32-35 = PETSCII, 0 = CP437). Explicit `--encoding` takes precedence. - **new**: `TRACE` log level (5, below `DEBUG`) with hexdump style output for all sent and received bytes. Use `--loglevel=trace`. - **bugfix**: `robot_check()` now uses a narrow character (space) instead of a wide Unicode character, allowing retro terminal emulators to pass. - **bugfix**: ATASCII codec now maps bytes 0x0D and 0x0A to CR and LF instead of graphics characters, fixing garbled output when connecting to Atari BBS systems. - **bugfix**: ATASCII codec normalizes CR and CRLF to the native ATASCII EOL (0x9B) during encoding, so the Return key works correctly. - **bugfix**: PETSCII bare CR (0x0D) is now normalized to CRLF in raw terminal mode and to LF in `telnetlib3-fingerprint` banners. - **bugfix**: `telnetlib3-fingerprint` re-encodes prompt responses for retro encodings so servers receive the correct EOL byte. - **bugfix**: `telnetlib3-fingerprint` no longer crashes with `LookupError` when the server negotiates an unknown charset. Banner formatting falls back to `latin-1`. - **bugfix**: `TelnetClient.send_charset()` normalises non-standard encoding names (`iso-8859-02` to `iso-8859-2`, `cp-1250` to `cp1250`, etc.). - **enhancement**: `telnetlib3-fingerprint` responds more like a terminal and to more y/n prompts about colors, encoding, etc. to collect more banners for the https://bbs.modem.xyz/ project. - **enhancement**: `telnetlib3-fingerprint` banner formatting uses `surrogateescape` error handler, preserving raw high bytes (e.g. CP437 art) as surrogates instead of replacing them with U+FFFD.
1 parent 7159647 commit 37a7226

27 files changed

Lines changed: 3100 additions & 231 deletions

README.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ program.
7272
telnetlib3-server
7373
# or custom port and ip and shell
7474
telnetlib3-server 0.0.0.0 1984 --shell=bin.server_wargame.shell
75-
# run an external program with a pseudo-terminal
76-
telnetlib3-server --pty-exec /bin/bash --pty-raw -- --login
77-
# or a simple linemode program, bc (calculator)
78-
telnetlib3-server --pty-exec /bin/bc
75+
# run an external program with a pseudo-terminal (raw mode is default)
76+
telnetlib3-server --pty-exec /bin/bash -- --login
77+
# or a linemode program, bc (calculator)
78+
telnetlib3-server --pty-exec /bin/bc --line-mode
7979

8080

8181
There are also fingerprinting CLIs, ``telnetlib3-fingerprint`` and

docs/guidebook.rst

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,50 @@ and utf-8 support.
205205
The same applies to clients -- ``open_connection(..., encoding=False)``
206206
returns a ``(TelnetReader, TelnetWriter)`` pair that works with ``bytes``.
207207

208+
Retro BBS Encodings
209+
~~~~~~~~~~~~~~~~~~~
210+
211+
telnetlib3 includes custom codecs for retro computing platforms commonly
212+
found on telnet BBS systems:
213+
214+
- **ATASCII** (``--encoding=atascii``) -- Atari 8-bit computers (400, 800,
215+
XL, XE). Graphics characters at 0x00-0x1F, card suits, box drawing, and
216+
an inverse-video range at 0x80-0xFF. The ATASCII end-of-line character
217+
(0x9B) maps to newline. Aliases: ``atari8bit``, ``atari_8bit``.
218+
- **PETSCII** (``--encoding=petscii``) -- Commodore 64/128 shifted
219+
(lowercase) mode. Lowercase a-z at 0x41-0x5A, uppercase A-Z at
220+
0xC1-0xDA. Aliases: ``cbm``, ``commodore``, ``c64``, ``c128``.
221+
- **Atari ST** (``--encoding=atarist``) -- Atari ST character set with
222+
extended Latin, Greek, and math symbols. Alias: ``atari``.
223+
224+
These encodings use bytes 0x80-0xFF for standard glyphs, which conflicts
225+
with the telnet protocol's default 7-bit NVT mode. When any of these
226+
encodings is selected, ``--force-binary`` is automatically enabled so that
227+
high-bit bytes are transmitted without requiring BINARY option negotiation.
228+
229+
PETSCII inline color codes are translated to ANSI 24-bit RGB using the
230+
VIC-II C64 palette, and cursor control codes (up/down/left/right, HOME,
231+
CLR, DEL) are translated to ANSI sequences. ATASCII control character
232+
glyphs (cursor movement, backspace, clear screen) are similarly translated.
233+
234+
Keyboard input is also mapped: arrow keys, backspace, delete, and enter
235+
produce the correct raw bytes for each encoding::
236+
237+
telnetlib3-client --encoding=atascii area52.tk 5200
238+
telnetlib3-client --encoding=petscii bbs.example.com 6400
239+
240+
``telnetlib3-fingerprint`` decodes and translates banners with these
241+
encodings, including PETSCII colors.
242+
243+
SyncTERM Font Detection
244+
^^^^^^^^^^^^^^^^^^^^^^^^
245+
246+
When a server sends a SyncTERM/CTerm font selection sequence
247+
(``CSI Ps1 ; Ps2 SP D``), both ``telnetlib3-client`` and
248+
``telnetlib3-fingerprint`` automatically switch the session encoding
249+
to match the font (e.g. font 36 = ATASCII, 32-35 = PETSCII, 0 = CP437).
250+
An explicit ``--encoding`` flag takes precedence over font detection.
251+
208252
Line Endings
209253
~~~~~~~~~~~~
210254

@@ -230,6 +274,44 @@ as-is -- it does **not** convert ``\n`` to ``\r\n``::
230274
For maximum compatibility with MUD clients, legacy terminals, and standard
231275
telnet implementations, always use ``\r\n`` with ``write()``.
232276

277+
Raw Mode and Line Mode
278+
~~~~~~~~~~~~~~~~~~~~~~
279+
280+
``telnetlib3-client`` defaults to **raw terminal mode** -- the local
281+
terminal is set to raw (no line buffering, no local echo, no signal
282+
processing), and each keystroke is sent to the server immediately. This
283+
is the correct mode for most BBS and MUD servers that handle their own
284+
echo and line editing.
285+
286+
Use ``--line-mode`` to switch to line-buffered input with local echo,
287+
which is appropriate for simple command-line services that expect the
288+
client to perform local line editing::
289+
290+
# Default: raw mode (correct for most servers)
291+
telnetlib3-client bbs.example.com
292+
293+
# Line mode: local echo and line buffering
294+
telnetlib3-client --line-mode simple-service.example.com
295+
296+
Similarly, ``telnetlib3-server --pty-exec`` defaults to raw PTY mode
297+
(disabling PTY echo), which is correct for programs that handle their own
298+
terminal I/O (curses, blessed, etc.). Use ``--line-mode`` for programs
299+
that expect cooked/canonical PTY mode::
300+
301+
# Default: raw PTY (correct for curses programs)
302+
telnetlib3-server --pty-exec /bin/bash -- --login
303+
304+
# Line mode: cooked PTY with echo (for simple programs like bc)
305+
telnetlib3-server --pty-exec /bin/bc --line-mode
306+
307+
Debugging
308+
~~~~~~~~~
309+
310+
Use ``--loglevel=trace`` to see hexdump-style output of all bytes sent
311+
and received on the wire::
312+
313+
telnetlib3-client --loglevel=trace --logfile=debug.log bbs.example.com
314+
233315
server_binary.py
234316
~~~~~~~~~~~~~~~~
235317

docs/history.rst

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
11
History
22
=======
3+
2.5.0
4+
* change: ``telnetlib3-client`` now defaults to raw terminal mode (no line buffering, no local
5+
echo), which is correct for most servers. Use ``--line-mode`` to restore line-buffered
6+
local-echo behavior.
7+
* change: ``telnetlib3-server --pty-exec`` now defaults to raw PTY mode. Use ``--line-mode`` to
8+
restore cooked PTY mode with echo.
9+
* change: ``connect_minwait`` default reduced to 0 across
10+
:class:`~telnetlib3.client_base.BaseClient`, :func:`~telnetlib3.client.open_connection`, and
11+
``telnetlib3-client``. Negotiation continues asynchronously. Use ``--connect-minwait`` to
12+
restore a delay if needed, or, use :meth:`~telnetlib3.stream_writer.TelnetWriter.wait_for` in
13+
server or client shells to await a specific negotiation state.
14+
* new: Color, keyboard input translation and ``--encoding`` support for ATASCII (ATARI ASCII) and
15+
PETSCII (Commodore ASCII).
16+
* new: SyncTERM/CTerm font selection sequence detection (``CSI Ps1 ; Ps2 SP D``). Both
17+
``telnetlib3-fingerprint`` and ``telnetlib3-client`` detect font switching and auto-switch
18+
encoding to the matching codec (e.g. font 36 = ATASCII, 32-35 = PETSCII, 0 = CP437). Explicit
19+
``--encoding`` takes precedence.
20+
* new: :data:`~telnetlib3.accessories.TRACE` log level (5, below ``DEBUG``) with
21+
:func:`~telnetlib3.accessories.hexdump` style output for all sent and received bytes. Use
22+
``--loglevel=trace``.
23+
* bugfix: :func:`~telnetlib3.guard_shells.robot_check` now uses a narrow
24+
character (space) instead of a wide Unicode character, allowing retro
25+
terminal emulators to pass.
26+
* bugfix: ATASCII codec now maps bytes 0x0D and 0x0A to CR and LF instead
27+
of graphics characters, fixing garbled output when connecting to Atari
28+
BBS systems.
29+
* bugfix: ATASCII codec normalizes CR and CRLF to the native ATASCII
30+
EOL (0x9B) during encoding, so the Return key works correctly.
31+
* bugfix: PETSCII bare CR (0x0D) is now normalized to CRLF in raw
32+
terminal mode and to LF in ``telnetlib3-fingerprint`` banners.
33+
* bugfix: ``telnetlib3-fingerprint`` re-encodes prompt responses for retro
34+
encodings so servers receive the correct EOL byte.
35+
* bugfix: ``telnetlib3-fingerprint`` no longer crashes with
36+
``LookupError`` when the server negotiates an unknown charset.
37+
Banner formatting falls back to ``latin-1``.
38+
* bugfix: :meth:`~telnetlib3.client.TelnetClient.send_charset` normalises
39+
non-standard encoding names (``iso-8859-02`` to ``iso-8859-2``,
40+
``cp-1250`` to ``cp1250``, etc.).
41+
* enhancement: ``telnetlib3-fingerprint`` responds more like a terminal and to more
42+
y/n prompts about colors, encoding, etc. to collect more banners for https://bbs.modem.xyz/
43+
project.
44+
* enhancement: ``telnetlib3-fingerprint`` banner formatting uses
45+
``surrogateescape`` error handler, preserving raw high bytes (e.g. CP437
46+
art) as surrogates instead of replacing them with U+FFFD.
347

448
2.4.0
549
* new: :mod:`telnetlib3.color_filter` module — translates 16-color ANSI SGR
@@ -28,7 +72,13 @@ History
2872
auto-answers yes/no, color, UTF-8 menu, ``who``, and ``help`` prompts.
2973
* enhancement: ``--banner-max-bytes`` option for ``telnetlib3-fingerprint``;
3074
default raised from 1024 to 65536.
31-
* enhancement: new ``--encoding=petscii`` and ``--encoding=atarist``
75+
* new: ATASCII (Atari 8-bit) codec -- ``--encoding=atascii`` for connecting
76+
to Atari BBS systems. Maps all 256 byte values to Unicode including
77+
graphics characters, card suits, and the inverse-video range (0x80-0xFF).
78+
ATASCII EOL (0x9B) maps to newline. Aliases: ``atari8bit``, ``atari_8bit``.
79+
* enhancement: ``--encoding=atascii``, ``--encoding=petscii``, and
80+
``--encoding=atarist`` now auto-enable ``--force-binary`` for both client
81+
and server, since these encodings use bytes 0x80-0xFF for standard glyphs.
3282
* bugfix: rare LINEMODE ACK loop with misbehaving servers that re-send
3383
unchanged MODE without ACK.
3484
* bugfix: unknown IAC commands no longer raise ``ValueError``; treated as

telnetlib3/accessories.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
import importlib
1010
from typing import TYPE_CHECKING, Any, Dict, Union, Mapping, Callable, Optional
1111

12+
#: Custom TRACE log level, below DEBUG (10).
13+
TRACE = 5
14+
logging.addLevelName(TRACE, "TRACE")
15+
1216
if TYPE_CHECKING: # pragma: no cover
1317
# local
1418
from .stream_reader import TelnetReader, TelnetReaderUnicode
1519

1620
__all__ = (
21+
"TRACE",
1722
"encoding_from_lang",
1823
"name_unicode",
1924
"eightbits",
25+
"hexdump",
2026
"make_logger",
2127
"repr_mapping",
2228
"function_lookup",
@@ -96,6 +102,29 @@ def eightbits(number: int) -> str:
96102
return f"0b{int(value):08d}"
97103

98104

105+
def hexdump(data: bytes, prefix: str = "") -> str:
106+
"""
107+
Format *data* as ``hexdump -C`` style output.
108+
109+
Each 16-byte row shows the offset, hex bytes grouped 8+8,
110+
and printable ASCII on the right::
111+
112+
00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 0d 0a |Hello World..|
113+
114+
:param data: Raw bytes to format.
115+
:param prefix: String prepended to every line (e.g. ``">> "``).
116+
:rtype: str
117+
"""
118+
lines: list[str] = []
119+
for offset in range(0, len(data), 16):
120+
chunk = data[offset : offset + 16]
121+
hex_left = " ".join(f"{b:02x}" for b in chunk[:8])
122+
hex_right = " ".join(f"{b:02x}" for b in chunk[8:])
123+
ascii_part = "".join(chr(b) if 0x20 <= b < 0x7F else "." for b in chunk)
124+
lines.append(f"{prefix}{offset:08x} {hex_left:<23s} {hex_right:<23s} |{ascii_part}|")
125+
return "\n".join(lines)
126+
127+
99128
_DEFAULT_LOGFMT = " ".join(
100129
("%(asctime)s", "%(levelname)s", "%(filename)s:%(lineno)d", "%(message)s")
101130
)
@@ -105,7 +134,9 @@ def make_logger(
105134
name: str, loglevel: str = "info", logfile: Optional[str] = None, logfmt: str = _DEFAULT_LOGFMT
106135
) -> logging.Logger:
107136
"""Create and return simple logger for given arguments."""
108-
lvl = getattr(logging, loglevel.upper())
137+
lvl = getattr(logging, loglevel.upper(), None)
138+
if lvl is None:
139+
lvl = logging.getLevelName(loglevel.upper())
109140

110141
_cfg: Dict[str, Any] = {"format": logfmt}
111142
if logfile:

0 commit comments

Comments
 (0)