Skip to content

Commit 2902206

Browse files
authored
TLS support (jquast#124)
- ssl support, even with the dangerous --ssl-no-verify - servers may also have plaintext or ssl support on the same port, with --tls-auto - small document refactor about preferring --shell in most of our examples, rather than manually writing so many ``if __name__ == '__main__':`` code blocks.
1 parent fc8ee4a commit 2902206

35 files changed

Lines changed: 1292 additions & 419 deletions

bin/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# bin/ package — shell callbacks for telnetlib3 examples.

bin/client_wargame.py

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,28 @@
1-
#!/usr/bin/env python
21
"""
3-
Telnet client that plays the "war game" against a server.
2+
Shell callback: telnet client that auto-answers the "war game".
43
5-
This example connects to a telnet server and automatically answers
6-
any question with 'y'. Run server_wargame.py first, then this client.
4+
Run ``server_wargame`` first, then this client.
75
8-
Example output::
6+
Usage::
7+
8+
telnetlib3-client --shell=bin.client_wargame.shell localhost 6023
99
10-
$ python client_wargame.py
10+
Example output::
1111
1212
Would you like to play a game? y
1313
They say the only way to win is to not play at all.
1414
"""
1515

16-
# std imports
17-
import asyncio
18-
19-
# local
20-
import telnetlib3
21-
2216

2317
async def shell(reader, writer):
2418
"""Handle client session, auto-answering questions."""
2519
while True:
26-
# Read stream until '?' mark is found
2720
outp = await reader.read(1024)
2821
if not outp:
29-
# End of File
3022
break
3123
if "?" in outp:
32-
# Reply to all questions with 'y'
3324
writer.write("y\r\n")
3425

35-
# Display all server output
3626
print(outp, flush=True, end="")
3727

38-
# EOF
3928
print()
40-
41-
42-
async def main():
43-
"""Connect to the telnet server."""
44-
_reader, writer = await telnetlib3.open_connection(host="localhost", port=6023, shell=shell)
45-
await writer.protocol.waiter_closed
46-
47-
48-
if __name__ == "__main__":
49-
asyncio.run(main())

bin/server_binary.py

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
#!/usr/bin/env python
21
"""
3-
Telnet server using binary (raw bytes) mode.
2+
Shell callback: binary (raw bytes) echo server.
43
5-
This example demonstrates using ``encoding=False`` for a server that works
6-
with raw bytes instead of Unicode strings. This is useful for protocol
7-
bridging, binary data transfer, or custom protocols over telnet.
4+
Usage::
85
9-
When encoding is set (the default), the shell callback receives
10-
``TelnetReaderUnicode`` and ``TelnetWriterUnicode``, which read and write
11-
``str``. When ``encoding=False``, the shell receives ``TelnetReader`` and
12-
``TelnetWriter``, which read and write ``bytes``.
6+
telnetlib3-server --encoding=false --shell=bin.server_binary.shell
137
14-
Run this server, then connect with: telnet localhost 6023
8+
When ``encoding=False``, the shell receives ``TelnetReader`` and
9+
``TelnetWriter``, which read and write ``bytes`` instead of ``str``.
1510
1611
Example session::
1712
@@ -23,12 +18,6 @@
2318
Connection closed by foreign host.
2419
"""
2520

26-
# std imports
27-
import asyncio
28-
29-
# local
30-
import telnetlib3 # pylint: disable=cyclic-import
31-
3221

3322
async def shell(reader, writer):
3423
"""Echo client input back as hex bytes."""
@@ -41,18 +30,3 @@ async def shell(reader, writer):
4130
writer.write(f"hex: {hex_str}\r\n".encode("ascii"))
4231
await writer.drain()
4332
writer.close()
44-
45-
46-
async def main():
47-
"""Start the telnet server in binary mode."""
48-
server = await telnetlib3.create_server(
49-
host="127.0.0.1", port=6023, shell=shell, encoding=False
50-
)
51-
print("Binary telnet server running on localhost:6023")
52-
print("Connect with: telnet localhost 6023")
53-
print("Press Ctrl+C to stop")
54-
await server.wait_closed()
55-
56-
57-
if __name__ == "__main__":
58-
asyncio.run(main())

bin/server_tls.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Shell callback: TLS-encrypted echo server (TELNETS).
3+
4+
Generate a self-signed certificate for testing::
5+
6+
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
7+
-days 365 -nodes -subj '/CN=localhost'
8+
9+
Usage::
10+
11+
telnetlib3-server --ssl-certfile cert.pem --ssl-keyfile key.pem \
12+
--shell=bin.server_tls.shell
13+
14+
Connect with the telnetlib3 client::
15+
16+
telnetlib3-client --ssl --ssl-cafile cert.pem localhost 6023
17+
18+
Or with openssl::
19+
20+
openssl s_client -connect localhost:6023 -quiet
21+
"""
22+
23+
24+
async def shell(reader, writer):
25+
"""Simple echo shell over TLS."""
26+
writer.write("Welcome to the TLS echo server!\r\n")
27+
await writer.drain()
28+
29+
while True:
30+
data = await reader.read(256)
31+
if not data:
32+
break
33+
writer.write(f"echo: {data}\r\n")
34+
await writer.drain()
35+
36+
writer.close()

bin/server_wait_for_negotiation.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
#!/usr/bin/env python
21
"""
3-
Telnet server demonstrating wait_for() negotiation states.
2+
Shell callback: wait for terminal negotiation states.
43
5-
This example shows how to use writer.wait_for() to await specific
6-
telnet option negotiation states before proceeding.
4+
Usage::
5+
6+
telnetlib3-server --shell=bin.server_wait_for_negotiation.shell
77
88
The server waits for:
9+
910
- NAWS (window size) to be negotiated
1011
- TTYPE (terminal type) negotiation to complete
1112
- BINARY mode (bidirectional)
@@ -14,9 +15,6 @@
1415
# std imports
1516
import asyncio
1617

17-
# local
18-
import telnetlib3
19-
2018

2119
async def shell(_reader, writer):
2220
"""Handle client with explicit negotiation waits."""
@@ -44,17 +42,3 @@ async def shell(_reader, writer):
4442
writer.write("\r\nNegotiation complete. Goodbye!\r\n")
4543
await writer.drain()
4644
writer.close()
47-
48-
49-
async def main():
50-
"""Start the telnet server."""
51-
server = await telnetlib3.create_server(host="127.0.0.1", port=6023, shell=shell)
52-
print("Negotiation demo server running on localhost:6023")
53-
await server.wait_closed()
54-
55-
56-
if __name__ == "__main__":
57-
try:
58-
asyncio.run(main())
59-
except KeyboardInterrupt:
60-
print("\nServer stopped")

bin/server_wargame.py

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
#!/usr/bin/env python
21
"""
3-
Telnet server that offers a basic "war game" question.
2+
Shell callback: simple "war game" question.
43
5-
This example demonstrates a simple telnet server using asyncio.
6-
Run this server, then connect with: telnet localhost 6023
4+
Usage::
5+
6+
telnetlib3-server --shell=bin.server_wargame.shell
7+
8+
Then connect with::
9+
10+
telnet localhost 6023
711
812
Example session::
913
@@ -15,12 +19,6 @@
1519
Connection closed by foreign host.
1620
"""
1721

18-
# std imports
19-
import asyncio
20-
21-
# local
22-
import telnetlib3 # pylint: disable=cyclic-import
23-
2422

2523
async def shell(reader, writer):
2624
"""Handle a single client connection."""
@@ -31,16 +29,3 @@ async def shell(reader, writer):
3129
writer.write("\r\nThey say the only way to win is to not play at all.\r\n")
3230
await writer.drain()
3331
writer.close()
34-
35-
36-
async def main():
37-
"""Start the telnet server."""
38-
server = await telnetlib3.create_server(host="127.0.0.1", port=6023, shell=shell)
39-
print("Telnet server running on localhost:6023")
40-
print("Connect with: telnet localhost 6023")
41-
print("Press Ctrl+C to stop")
42-
await server.wait_closed()
43-
44-
45-
if __name__ == "__main__":
46-
asyncio.run(main())

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@
6868
# built documents.
6969
#
7070
# The short X.Y version.
71-
version = "2.5"
71+
version = "2.6"
7272

7373
# The full version, including alpha/beta/rc tags.
74-
release = "2.5.0" # keep in sync with pyproject.toml and telnetlib3/accessories.py !!
74+
release = "2.6.0" # keep in sync with pyproject.toml and telnetlib3/accessories.py !!
7575

7676
# The language for content auto-generated by Sphinx. Refer to documentation
7777
# for a list of supported languages.

0 commit comments

Comments
 (0)