Claude Code for telnetlib: Python Telnet Client — Claude Skills 360 Blog
Blog / AI / Claude Code for telnetlib: Python Telnet Client
AI

Claude Code for telnetlib: Python Telnet Client

Published: November 4, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s telnetlib module implements the Telnet protocol client, primarily used for automating legacy network device CLI access, serial console servers, and expect-style automation. import telnetlib. Connect: t = telnetlib.Telnet(host, port=23, timeout=10) or use as context manager. Read: t.read_until(b"expected", timeout) → bytes (returns everything up to and including the match, or everything received if timeout); t.read_all() → bytes until EOF; t.read_some() → at least 1 byte; t.read_very_eager() → everything available. Write: t.write(b"command\n") — automatically escapes IAC bytes. Expect: t.expect([re_pat1, re_pat2], timeout)(match_index, match_obj, text_before). Option negotiation: t.set_option_negotiation_callback(fn) — fn(sock, cmd, option) called for each IAC negotiation. Interactive: t.interact() — passes stdin/stdout. Telnet control: telnetlib.IAC, WILL, WONT, DO, DONT, SB, SE, NOP — byte constants for IAC sequences. Encoding: Telnet is byte-oriented; decode received bytes with .decode("ascii", errors="replace"). Close: t.close(). Note: deprecated 3.11, removed 3.13 — include compatibility guard; use asyncio + raw TCP or paramiko (SSH) for new code. Claude Code generates automated login scripts, CLI scrapers, expect-style automation tools, and legacy device configuration managers.

CLAUDE.md for telnetlib

## telnetlib Stack
- Stdlib: import telnetlib  (deprecated 3.11, removed 3.13 — guard with try/except)
- Connect: t = telnetlib.Telnet(host, port=23, timeout=10)
- Read:    t.read_until(b"login: ", timeout=5)    # blocks until match or timeout
-          t.read_very_eager()                     # non-blocking, whatever's buffered
- Write:   t.write(b"admin\r\n")                  # \r\n for Telnet line endings
- Expect:  idx, match, text = t.expect([rb"\\$", rb"#"], timeout=10)
- Close:   t.close()  (or use as context manager)

telnetlib Telnet Automation Pipeline

# app/telnetlibutil.py — connect, login, expect, scrape, option negotiate
from __future__ import annotations

import io
import re
import socket
import time
from dataclasses import dataclass, field
from typing import Callable

# Guard for Python 3.13+ where telnetlib is removed
try:
    import telnetlib as _telnetlib
    _TELNETLIB_AVAILABLE = True
except ImportError:
    _TELNETLIB_AVAILABLE = False


# ─────────────────────────────────────────────────────────────────────────────
# 1. Connection and basic I/O
# ─────────────────────────────────────────────────────────────────────────────

def connect(
    host: str,
    port: int = 23,
    timeout: int = 10,
):
    """
    Open a Telnet connection. Returns a Telnet object (use as context manager).

    Example:
        with connect("192.168.1.1") as t:
            data = t.read_until(b"login: ", timeout=5)
    """
    if not _TELNETLIB_AVAILABLE:
        raise ImportError("telnetlib not available (Python 3.13+)")
    return _telnetlib.Telnet(host, port, timeout)


def read_until_decoded(
    t,
    expected: str | bytes,
    timeout: float = 10.0,
    encoding: str = "ascii",
) -> str:
    """
    Call read_until and decode the received bytes.

    Example:
        prompt = read_until_decoded(t, "login:", timeout=5)
    """
    if isinstance(expected, str):
        expected = expected.encode(encoding)
    raw = t.read_until(expected, timeout)
    return raw.decode(encoding, errors="replace")


def writeline(t, text: str, encoding: str = "ascii") -> None:
    """
    Write a line followed by CR+LF (Telnet standard line ending).
    Automatically escapes IAC bytes.

    Example:
        writeline(t, "admin")
    """
    t.write(text.encode(encoding) + b"\r\n")


def read_flush(t, pause: float = 0.1) -> str:
    """
    Wait briefly then read all buffered data. Returns decoded ASCII string.

    Example:
        writeline(t, "show version")
        output = read_flush(t, pause=0.5)
    """
    time.sleep(pause)
    raw = t.read_very_eager()
    return raw.decode("ascii", errors="replace")


# ─────────────────────────────────────────────────────────────────────────────
# 2. Expect-style automation
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class ExpectResult:
    matched_index: int       # which pattern matched (-1 if timeout)
    matched_text:  str       # text before and including the match
    match_obj:     re.Match | None  # regex match object

    @property
    def timed_out(self) -> bool:
        return self.matched_index == -1

    def __str__(self) -> str:
        if self.timed_out:
            return f"ExpectResult(TIMEOUT, got={self.matched_text[:60]!r})"
        return (f"ExpectResult(match={self.matched_index}, "
                f"text={self.matched_text[:60]!r})")


def expect(
    t,
    patterns: list[str | bytes],
    timeout: float = 10.0,
    encoding: str = "ascii",
) -> ExpectResult:
    """
    Wait for one of several regex patterns to appear in the input.
    Returns an ExpectResult.

    Example:
        result = expect(t, [r"\\$", r"#", r"login failed"], timeout=10)
        if result.matched_index == 0:
            print("shell prompt")
        elif result.timed_out:
            print("no prompt received")
    """
    compiled = []
    for p in patterns:
        if isinstance(p, str):
            compiled.append(re.compile(p.encode(encoding)))
        else:
            compiled.append(re.compile(p))

    idx, m, text = t.expect(compiled, timeout)
    return ExpectResult(
        matched_index=idx,
        matched_text=text.decode(encoding, errors="replace"),
        match_obj=m,
    )


# ─────────────────────────────────────────────────────────────────────────────
# 3. Login helper
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class LoginConfig:
    username:        str
    password:        str
    login_prompt:    str = "login:"
    password_prompt: str = "Password:"
    shell_prompt:    str = r"[\\$#>%] ?$"   # regex
    encoding:        str = "ascii"
    timeout:         float = 10.0


def telnet_login(t, cfg: LoginConfig) -> bool:
    """
    Perform a standard Telnet login sequence.
    Returns True on success (shell prompt seen), False on failure.

    Example:
        cfg = LoginConfig(username="admin", password="secret")
        if telnet_login(t, cfg):
            writeline(t, "show interfaces")
        else:
            print("Login failed")
    """
    # Wait for login prompt
    r = expect(t, [cfg.login_prompt, "refused", "error"],
               timeout=cfg.timeout, encoding=cfg.encoding)
    if r.timed_out or r.matched_index != 0:
        return False

    # Send username
    writeline(t, cfg.username, encoding=cfg.encoding)

    # Wait for password prompt
    r = expect(t, [cfg.password_prompt, "incorrect", "denied"],
               timeout=cfg.timeout, encoding=cfg.encoding)
    if r.timed_out or r.matched_index != 0:
        return False

    # Send password
    writeline(t, cfg.password, encoding=cfg.encoding)

    # Wait for shell prompt
    r = expect(t, [cfg.shell_prompt, "incorrect", "denied", "failed"],
               timeout=cfg.timeout, encoding=cfg.encoding)
    return not r.timed_out and r.matched_index == 0


# ─────────────────────────────────────────────────────────────────────────────
# 4. Command scraper
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class CommandResult:
    command: str
    output:  str
    elapsed: float

    def __str__(self) -> str:
        preview = self.output.strip()[:80].replace("\n", "↵")
        return f"[{self.elapsed:.2f}s] {self.command!r}: {preview!r}"


def run_command(
    t,
    command: str,
    prompt_pattern: str = r"[\\$#>%] ?$",
    timeout: float = 15.0,
    encoding: str = "ascii",
) -> CommandResult:
    """
    Send a command and collect output until the prompt reappears.

    Example:
        result = run_command(t, "show version")
        print(result.output)
    """
    t_start = time.monotonic()
    writeline(t, command, encoding=encoding)
    r = expect(t, [prompt_pattern], timeout=timeout, encoding=encoding)
    elapsed = time.monotonic() - t_start
    return CommandResult(
        command=command,
        output=r.matched_text,
        elapsed=elapsed,
    )


def run_commands(
    t,
    commands: list[str],
    prompt_pattern: str = r"[\\$#>%] ?$",
    timeout: float = 15.0,
) -> list[CommandResult]:
    """
    Run a sequence of commands, collecting output after each.

    Example:
        results = run_commands(t, ["hostname", "uptime", "df -h"])
        for r in results:
            print(r)
    """
    return [
        run_command(t, cmd, prompt_pattern=prompt_pattern, timeout=timeout)
        for cmd in commands
    ]


# ─────────────────────────────────────────────────────────────────────────────
# 5. IAC option negotiation helper
# ─────────────────────────────────────────────────────────────────────────────

def refuse_all_options(sock, cmd: bytes, option: bytes) -> None:
    """
    Option negotiation callback that refuses all Telnet option offers.
    Pass as: t.set_option_negotiation_callback(refuse_all_options)

    Example:
        t = telnetlib.Telnet(host)
        t.set_option_negotiation_callback(refuse_all_options)
    """
    if not _TELNETLIB_AVAILABLE:
        return
    IAC  = _telnetlib.IAC
    DO   = _telnetlib.DO
    DONT = _telnetlib.DONT
    WILL = _telnetlib.WILL
    WONT = _telnetlib.WONT

    if cmd == DO:
        sock.sendall(IAC + WONT + option)
    elif cmd == WILL:
        sock.sendall(IAC + DONT + option)


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== telnetlib demo ===")

    if not _TELNETLIB_AVAILABLE:
        print("  telnetlib not available (Python 3.13+)")
        print("  Demonstrating LoginConfig and CommandResult only:")
        cfg = LoginConfig(username="admin", password="secret",
                          login_prompt="login:", password_prompt="Password:")
        print(f"  LoginConfig: {cfg}")
        r = CommandResult(command="show version",
                          output="Device version 1.0\nUptime: 5 days\n$ ",
                          elapsed=0.42)
        print(f"  CommandResult: {r}")
        raise SystemExit(0)

    # Try towel.blinkenlights.nl (a famous public Telnet ASCII art server)
    HOST = "towel.blinkenlights.nl"
    PORT = 23
    print(f"\n  Attempting to connect to {HOST}:{PORT} (ASCII Star Wars) ...")
    print("  (If no network, skipping live demo)")

    try:
        with connect(HOST, PORT, timeout=8) as t:
            t.set_option_negotiation_callback(refuse_all_options)

            # Read the opening banner (first 500 bytes)
            time.sleep(1.0)
            data = t.read_very_eager()
            banner = data.decode("ascii", errors="replace")
            print(f"\n--- banner (first {min(len(banner), 200)} chars) ---")
            print(banner[:200])
            print(f"\n  (total received: {len(data)} bytes)")

    except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e:
        print(f"  Network unavailable: {e}")
        print("  (telnetlib is available, just no network in this environment)")

    # ── demonstrate expect patterns (offline) ─────────────────────────────────
    print("\n--- ExpectResult demo (no network needed) ---")
    results = [
        ExpectResult(matched_index=0, matched_text="root@server:~$ ",
                     match_obj=re.search(rb"\$", b"root@server:~$ ")),
        ExpectResult(matched_index=-1, matched_text="",
                     match_obj=None),
    ]
    for r in results:
        print(f"  {r}")

    print("\n=== done ===")

For the paramiko (PyPI) alternative — paramiko.SSHClient().connect(host, username, password) followed by chan = client.invoke_shell() provides an SSH-encrypted interactive session with the same read/write/expect loop that telnetlib uses for Telnet — use paramiko for all new remote CLI automation since SSH is encrypted and Telnet transmits credentials in plaintext; use telnetlib only when the target device genuinely speaks Telnet on port 23 (legacy routers, serial-to-Ethernet adapters, mainframe consoles) and SSH is not available. For the asyncio + raw TCP alternative — asyncio.open_connection(host, port) gives you an async reader/writer pair for any TCP protocol, and asyncio.wait_for(reader.readuntil(sep), timeout) replicates read_until() — use asyncio.open_connection() for new Telnet-protocol code in Python 3.13+ since telnetlib is removed; the IAC protocol negotiation and byte escaping need to be reimplemented manually, but the basic send/expect loop maps directly. The Claude Skills 360 bundle includes telnetlib skill sets covering connect() with refuse_all_options() IAC callback, read_until_decoded()/writeline()/read_flush() I/O helpers, ExpectResult with expect() pattern matching, LoginConfig with telnet_login() automated login, and CommandResult with run_command()/run_commands() CLI scraper. Start with the free tier to try Telnet automation patterns and telnetlib pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free