telnetlib3

repository·master·Indexed 41 days ago

https://github.com/jquast/telnetlib3

A feature-rich Telnet server, client, and protocol library for Python 3.9+. Version 5.0.1 supports both modern asyncio and legacy blocking APIs, providing a backport for the removed standard telnetlib module. It includes tools for implementing synchronous and asynchronous servers, a CLI client, and utilities for moderating fingerprint data.

Tokens
40.9K
Snippets
123
Records
186
Agent score
51%

What's inside telnetlib3

  1. Use the Asyncio Interface for concurrent connections

    master
    The primary interface for telnetlib3 is built on Python's asyncio library. This allows the library to handle many concurrent Telnet connections efficiently within a single process or thread using asynchronous I/O.
  2. Configure Go-Ahead (IAC-GA) behavior

    master

    The IAC-GA (Go-Ahead) signal is used to coordinate transmission on half-duplex protocols. In modern TCP-based Telnet, this is largely unnecessary.

    • Default Behavior: telnetlib3 suppresses the IAC-GA signal by default by sending IAC-WILL-SGA.
    • Enabling Go-Ahead: If your client requires the IAC-GA signal (common in some MUDs or legacy 1970-era hosts like AMES-67), the client must explicitly request it by sending IAC-DONT-SGA.
  3. Handle Unicode vs. Binary data modes

    master

    By default, telnetlib3 uses encoding="utf8". In this mode, the shell callback receives TelnetReaderUnicode and TelnetWriterUnicode objects, which work with Python str objects.

    To work with raw bytes (useful for low-level protocols like XMODEM or legacy systems), pass encoding=False to create_server or open_connection. The shell will then receive TelnetReader and TelnetWriter objects which work with bytes.

    Important: Line Endings Regardless of mode, the Telnet protocol (RFC 854) requires \r\n (CR LF) for NVT output. The write() method does not automatically convert \n to \r\n. Always use \r\n for maximum compatibility.

    # Unicode Mode (Default)
    async def shell(reader, writer):
        writer.write("Hello, world!\r\n")  # writes str
        data = await reader.read(1)         # returns str
    
    # Binary Mode
    async def binary_shell(reader, writer):
        writer.write(b"Hello, world!\r\n")  # writes bytes
        data = await reader.read(1)         # returns bytes
    
    await telnetlib3.create_server(
        host="127.0.0.1", port=6023,
        shell=binary_shell, encoding=False
    )
  4. Manage MCCP compression

    master

    The library supports MCCP2 (server-to-client) and MCCP3 (client-to-server) zlib compression, commonly used by MUD servers to reduce bandwidth.

    • Client behavior: By default, the client passively accepts compression if offered. Use --compression to actively request it or --no-compression to reject it.
    • Server behavior: By default, the server does not advertise compression. Use --compression to advertise MCCP2/MCCP3 support.
    • Note: Compression is automatically disabled over TLS connections.
    # Actively request compression
    telnetlib3-client --compression dunemud.net 6789
    
    # Reject compression
    telnetlib3-client --no-compression dunemud.net 6789
    
    # Host a MUD server that advertises MCCP2/MCCP3
    telnetlib3-server --compression --shell=my_mud.shell
  5. Understand Special Linemode Character (SLC) and Remote Line Editing

    master

    This project implements Special Linemode Character (SLC) negotiation and Remote Line Editing (rfc-1184).

    Remote line editing allows for low-latency output on slow network links by allowing incomplete lines to be buffered while providing remote editing facilities (e.g., Backspace, Interrupt Process ^C, Repaint ^R, Erase Word ^W). The Server and Client agree on a series of SLC function values to map keyboard characters to these editing actions.

  6. Handle end-of-line signaling and carriage returns

    master

    The default implementation of telnetlib3 provides remote line editing and triggers the line_received callback when it detects various end-of-line sequences.

    Supported signaling mechanisms include:

    • CR LF (Carriage Return, Linefeed): The standard Telnet end-of-line. The default implementation strips this and fires line_received on the CR byte.
    • CR NUL (Carriage Return, Null): Often used to distinguish between 'return' and 'enter' keys. The default implementation strips this and fires line_received on the CR byte.
    • CR (Carriage Return): Handled by stripping the byte and firing line_received.
    • LF (Linefeed): Handled by stripping the byte and firing line_received.

    Note for Implementers: If you need to distinguish between these specific sequences (e.g., for raw file transfers or differentiating 'return' vs 'enter'), do not rely on the default line_received behavior. Instead, you should directly override data_received, or derive your own implementations of editing_received and character_received.

  7. Enable Kludge Mode for remote editing

    master

    Kludge mode provides a simulated Special Linemode Character (SLC) experience for remote editing, offering an experience similar to readline for most Telnet clients. This is particularly useful for clients like Windows 98 telnet, SyncTerm, netrunner, or TinTin++ to enable character-at-a-time editing.

    To enable kludge mode, the server sends the following sequence:

    1. WILL-SGA (Suppress Go-Ahead)
    2. WILL-ECHO

    Note: In kludge mode, because SGA is enabled, there is no deterministic way to know if a remote program is hung, processing, or has output pending in the transport layer. This can lead to user frustration on high-latency links.

    Sequence: WILL-SGA, WILL-ECHO
  8. Migrate from miniboa using ServerConnection

    master

    If you are migrating from miniboa, the telnetlib3.sync.ServerConnection class (provided to your handler) offers several compatible properties and methods:

    Compatible Properties and Methods

    • active: Connection status
    • address: Connection address
    • port: Connection port
    • terminal_type: Terminal type
    • columns / rows: Terminal dimensions
    • send(data): Sends data, automatically normalizing \n to \r\n for miniboa compatibility.
    • addrport(): Returns address and port.
    • idle(): Returns idle time.
    • duration(): Returns connection duration.
    • deactivate(): Deactivates the connection.

    Key Differences

    • Threading: telnetlib3 uses a thread-per-connection model instead of miniboa's poll-based loop.
    • Reading: Use blocking readline() or read() instead of get_command() or cmd_ready.
    from telnetlib3.sync import BlockingTelnetServer
    
    def handler(client):
        # Miniboa-compatible properties
        print(f"Connected: {client.addrport()}")
        print(f"Terminal: {client.terminal_type}")
        print(f"Size: {client.columns}x{client.rows}")
    
        # Miniboa-compatible send (converts \n to \r\n)
        client.send("Welcome!\n")
    
        while client.active:
            if client.idle() > 300:
                client.send("Timeout.\n")
                client.deactivate()
                break
    
            try:
                line = client.readline(timeout=1)
            except TimeoutError:
                continue
            if line:
                client.send(f"Echo: {line}")
    
    server = BlockingTelnetServer('0.0.0.0', 6023, handler=handler)
    server.serve_forever()
  9. How to gracefully close a connection

    master

    When using telnetlib3, application code should not call reader.close(). Instead, to gracefully close a connection, call writer.close() and then await writer.wait_closed(). This action signals end-of-input to the reader via the protocol layer. Once the underlying transport indicates EOF, the reader is marked as EOF, and any pending read coroutines are woken up. Subsequent calls to read() will return b"" once the buffer is drained, and readline() or iteration will stop.

    async def app(reader, writer):
        # ... use reader/readline/readuntil ...
        writer.close()
        await writer.wait_closed()
        # reader will eventually see EOF; reads return b"" once buffer drains
  10. Implement TLS/SSL for Telnet connections

    master

    Telnet over TLS (TELNETS) encrypts the connection from the start. This is not STARTTLS.

    Server-side:

    • Use --ssl-certfile and --ssl-keyfile via CLI.
    • Programmatically, pass an ssl.SSLContext to the ssl parameter in create_server.
    • Use --tls-auto to allow both TLS and plain telnet on the same port. The server peeks at the first byte to detect a TLS ClientHello.

    Client-side:

    • Use --ssl to connect to a server with a CA-signed certificate.
    • Use --ssl --ssl-cafile cert.pem for self-signed certificates.
    • Use --ssl-no-verify to skip certificate verification (insecure, use for testing only).
    • Programmatically, pass ssl=True or a custom ssl.SSLContext to open_connection.
    # Programmatic TLS Server
    import ssl
    import telnetlib3
    
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain("cert.pem", keyfile="key.pem")
    
    server = await telnetlib3.create_server(host="0.0.0.0", port=6023, shell=shell, ssl=ctx)
    
    # Programmatic TLS Client (CA-signed)
    reader, writer = await telnetlib3.open_connection("dunemud.net", 6788, ssl=True)
    
    # Programmatic TLS Client (Self-signed)
    ctx = ssl.create_default_context(cafile="cert.pem")
    reader, writer = await telnetlib3.open_connection("localhost", 6023, ssl=ctx)
  11. Set up a developer environment for telnetlib3

    master

    To develop on telnetlib3, install the package in editable mode from the project root. This ensures that any changes made to the source code are immediately available to the Python interpreter as the telnetlib3 module, regardless of your current working directory.

    pip install --editable .
  12. Use the blocking (synchronous) interface

    master

    If asyncio is too complex for your application, telnetlib3 provides a synchronous interface via telnetlib3.sync. This interface runs an asyncio event loop in a background thread, allowing you to use familiar blocking methods.

    Client Usage

    Use TelnetConnection to create a blocking client. It is recommended to use it as a context manager to ensure proper cleanup.

    Server Usage

    Use BlockingTelnetServer to create a blocking server. By default, it uses a thread-per-connection model, spawning a new thread for every client that connects.

    from telnetlib3.sync import TelnetConnection
    
    # Recommended: Using context manager
    with TelnetConnection('localhost', 6023) as conn:
        conn.write('hello\r\n')
        response = conn.readline()
        print(response)
    
    # Alternative: Manual lifecycle
    conn = TelnetConnection('localhost', 6023, encoding='utf8')
    conn.connect()
    try:
        conn.write('command\r\n')
        data = conn.read_until(b'>>> ')
        print(data)
    finally:
        conn.close()