mirror of https://github.com/python/cpython
Decided to add the telnet library that I wrote long ago (it's still in
the Demos/cwilib directory). Converted comments to doc strings and used default arguments instead of *args. Updated the example.
This commit is contained in:
parent
dd79bd3539
commit
b9b50eb7e0
|
@ -0,0 +1,424 @@
|
|||
"""TELNET client class.
|
||||
|
||||
Based on RFC 854: TELNET Protocol Specification, by J. Postel and
|
||||
J. Reynolds
|
||||
|
||||
Example:
|
||||
|
||||
>>> from telnetlib import Telnet
|
||||
>>> tn = Telnet('www.python.org', 79) # connect to finger port
|
||||
>>> tn.write('guido\r\n')
|
||||
>>> print tn.read_all()
|
||||
Login Name TTY Idle When Where
|
||||
guido Guido van Rossum pts/2 <Dec 2 11:10> snag.cnri.reston..
|
||||
|
||||
>>>
|
||||
|
||||
Note that read_all() won't read until eof -- it just reads some data
|
||||
-- but it guarantees to read at least one byte unless EOF is hit.
|
||||
|
||||
It is possible to pass a Telnet object to select.select() in order to
|
||||
wait until more data is available. Note that in this case,
|
||||
read_eager() may return '' even if there was data on the socket,
|
||||
because the protocol negotiation may have eaten the data. This is why
|
||||
EOFError is needed in some cases to distinguish between "no data" and
|
||||
"connection closed" (since the socket also appears ready for reading
|
||||
when it is closed).
|
||||
|
||||
Bugs:
|
||||
- may hang when connection is slow in the middle of an IAC sequence
|
||||
|
||||
To do:
|
||||
- option negotiation
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Imported modules
|
||||
import socket
|
||||
import select
|
||||
import string
|
||||
import regsub
|
||||
|
||||
# Tunable parameters
|
||||
DEBUGLEVEL = 0
|
||||
|
||||
# Telnet protocol defaults
|
||||
TELNET_PORT = 23
|
||||
|
||||
# Telnet protocol characters (don't change)
|
||||
IAC = chr(255) # "Interpret As Command"
|
||||
DONT = chr(254)
|
||||
DO = chr(253)
|
||||
WONT = chr(252)
|
||||
WILL = chr(251)
|
||||
theNULL = chr(0)
|
||||
|
||||
|
||||
class Telnet:
|
||||
|
||||
"""Telnet interface class.
|
||||
|
||||
An instance of this class represents a connection to a telnet
|
||||
server. The instance is initially not connected; the open()
|
||||
method must be used to establish a connection. Alternatively, the
|
||||
host name and optional port number can be passed to the
|
||||
constructor, too.
|
||||
|
||||
Don't try to reopen an already connected instance.
|
||||
|
||||
This class has many read_*() methods. Note that some of them
|
||||
raise EOFError when the end of the connection is read, because
|
||||
they can return an empty string for other reasons. See the
|
||||
individual doc strings.
|
||||
|
||||
read_until(expected, [timeout])
|
||||
Read until the expected string has been seen, or a timeout is
|
||||
hit (default is no timeout); may block.
|
||||
|
||||
read_all()
|
||||
Read all data until EOF; may block.
|
||||
|
||||
read_some()
|
||||
Read at least one byte or EOF; may block.
|
||||
|
||||
read_very_eager()
|
||||
Read all data available already queued or on the socket,
|
||||
without blocking.
|
||||
|
||||
read_eager()
|
||||
Read either data already queued or some data available on the
|
||||
socket, without blocking.
|
||||
|
||||
read_lazy()
|
||||
Read all data in the raw queue (processing it first), without
|
||||
doing any socket I/O.
|
||||
|
||||
read_very_lazy()
|
||||
Reads all data in the cooked queue, without doing any socket
|
||||
I/O.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, host=None, port=0):
|
||||
"""Constructor.
|
||||
|
||||
When called without arguments, create an unconnected instance.
|
||||
With a hostname argument, it connects the instance; a port
|
||||
number is optional.
|
||||
|
||||
"""
|
||||
self.debuglevel = DEBUGLEVEL
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.sock = None
|
||||
self.rawq = ''
|
||||
self.irawq = 0
|
||||
self.cookedq = ''
|
||||
self.eof = 0
|
||||
if host:
|
||||
self.open(host, port)
|
||||
|
||||
def open(self, host, port=0):
|
||||
"""Connect to a host.
|
||||
|
||||
The optional second argument is the port number, which
|
||||
defaults to the standard telnet port (23).
|
||||
|
||||
Don't try to reopen an already connected instance.
|
||||
|
||||
"""
|
||||
self.eof = 0
|
||||
if not port:
|
||||
port = TELNET_PORT
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect((self.host, self.port))
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor -- close the connection."""
|
||||
self.close()
|
||||
|
||||
def msg(self, msg, *args):
|
||||
"""Print a debug message, when the debug level is > 0.
|
||||
|
||||
If extra arguments are present, they are substituted in the
|
||||
message using the standard string formatting operator.
|
||||
|
||||
"""
|
||||
if self.debuglevel > 0:
|
||||
print 'Telnet(%s,%d):' % (self.host, self.port),
|
||||
if args:
|
||||
print msg % args
|
||||
else:
|
||||
print msg
|
||||
|
||||
def set_debuglevel(self, debuglevel):
|
||||
"""Set the debug level.
|
||||
|
||||
The higher it is, the more debug output you get (on sys.stdout).
|
||||
|
||||
"""
|
||||
self.debuglevel = debuglevel
|
||||
|
||||
def close(self):
|
||||
"""Close the connection."""
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = 0
|
||||
self.eof = 1
|
||||
|
||||
def get_socket(self):
|
||||
"""Return the socket object used internally."""
|
||||
return self.sock
|
||||
|
||||
def fileno(self):
|
||||
"""Return the fileno() of the socket object used internally."""
|
||||
return self.sock.fileno()
|
||||
|
||||
def write(self, buffer):
|
||||
"""Write a string to the socket, doubling any IAC characters.
|
||||
|
||||
Can block if the connection is blocked. May raise
|
||||
socket.error if the connection is closed.
|
||||
|
||||
"""
|
||||
if IAC in buffer:
|
||||
buffer = regsub.gsub(IAC, IAC+IAC, buffer)
|
||||
self.sock.send(buffer)
|
||||
|
||||
def read_until(self, match, timeout=None):
|
||||
"""Read until a given string is encountered or until timeout.
|
||||
|
||||
When no match is found, return whatever is available instead,
|
||||
possibly the empty string. Raise EOFError if the connection
|
||||
is closed and no cooked data is available.
|
||||
|
||||
"""
|
||||
n = len(match)
|
||||
self.process_rawq()
|
||||
i = string.find(self.cookedq, match)
|
||||
if i >= 0:
|
||||
i = i+n
|
||||
buf = self.cookedq[:i]
|
||||
self.cookedq = self.cookedq[i:]
|
||||
return buf
|
||||
s_reply = ([self], [], [])
|
||||
s_args = s_reply
|
||||
if timeout is not None:
|
||||
s_args = s_args + (timeout,)
|
||||
while not self.eof and apply(select.select, s_args) == s_reply:
|
||||
i = max(0, len(self.cookedq)-n)
|
||||
self.fill_rawq()
|
||||
self.process_rawq()
|
||||
i = string.find(self.cookedq, match, i)
|
||||
if i >= 0:
|
||||
i = i+n
|
||||
buf = self.cookedq[:i]
|
||||
self.cookedq = self.cookedq[i:]
|
||||
return buf
|
||||
return self.read_very_lazy()
|
||||
|
||||
def read_all(self):
|
||||
"""Read all data until EOF; block until connection closed."""
|
||||
self.process_rawq()
|
||||
while not self.eof:
|
||||
self.fill_rawq()
|
||||
self.process_rawq()
|
||||
buf = self.cookedq
|
||||
self.cookedq = ''
|
||||
return buf
|
||||
|
||||
def read_some(self):
|
||||
"""Read at least one byte of cooked data unless EOF is hit.
|
||||
|
||||
Return '' if EOF is hit. Block if no data is immediately
|
||||
available.
|
||||
|
||||
"""
|
||||
self.process_rawq()
|
||||
while not self.cookedq and not self.eof:
|
||||
self.fill_rawq()
|
||||
self.process_rawq()
|
||||
buf = self.cookedq
|
||||
self.cookedq = ''
|
||||
return buf
|
||||
|
||||
def read_very_eager(self):
|
||||
"""Read everything that's possible without blocking in I/O (eager).
|
||||
|
||||
Raise EOFError if connection closed and no cooked data
|
||||
available. Return '' if no cooked data available otherwise.
|
||||
Don't block unless in the midst of an IAC sequence.
|
||||
|
||||
"""
|
||||
self.process_rawq()
|
||||
while not self.eof and self.sock_avail():
|
||||
self.fill_rawq()
|
||||
self.process_rawq()
|
||||
return self.read_very_lazy()
|
||||
|
||||
def read_eager(self):
|
||||
"""Read readily available data.
|
||||
|
||||
Raise EOFError if connection closed and no cooked data
|
||||
available. Return '' if no cooked data available otherwise.
|
||||
Don't block unless in the midst of an IAC sequence.
|
||||
|
||||
"""
|
||||
self.process_rawq()
|
||||
while not self.cookedq and not self.eof and self.sock_avail():
|
||||
self.fill_rawq()
|
||||
self.process_rawq()
|
||||
return self.read_very_lazy()
|
||||
|
||||
def read_lazy(self):
|
||||
"""Process and return data that's already in the queues (lazy).
|
||||
|
||||
Raise EOFError if connection closed and no data available.
|
||||
Return '' if no cooked data available otherwise. Don't block
|
||||
unless in the midst of an IAC sequence.
|
||||
|
||||
"""
|
||||
self.process_rawq()
|
||||
return self.read_very_lazy()
|
||||
|
||||
def read_very_lazy(self):
|
||||
"""Return any data available in the cooked queue (very lazy).
|
||||
|
||||
Raise EOFError if connection closed and no data available.
|
||||
Return '' if no cooked data available otherwise. Don't block.
|
||||
|
||||
"""
|
||||
buf = self.cookedq
|
||||
self.cookedq = ''
|
||||
if not buf and self.eof and not self.rawq:
|
||||
raise EOFError, 'telnet connection closed'
|
||||
return buf
|
||||
|
||||
def process_rawq(self):
|
||||
"""Transfer from raw queue to cooked queue.
|
||||
|
||||
Set self.eof when connection is closed. Don't block unless in
|
||||
the midst of an IAC sequence.
|
||||
|
||||
"""
|
||||
buf = ''
|
||||
try:
|
||||
while self.rawq:
|
||||
c = self.rawq_getchar()
|
||||
if c == theNULL:
|
||||
continue
|
||||
if c == "\021":
|
||||
continue
|
||||
if c != IAC:
|
||||
buf = buf + c
|
||||
continue
|
||||
c = self.rawq_getchar()
|
||||
if c == IAC:
|
||||
buf = buf + c
|
||||
elif c in (DO, DONT):
|
||||
opt = self.rawq_getchar()
|
||||
self.msg('IAC %s %d', c == DO and 'DO' or 'DONT', ord(c))
|
||||
self.sock.send(IAC + WONT + opt)
|
||||
elif c in (WILL, WONT):
|
||||
opt = self.rawq_getchar()
|
||||
self.msg('IAC %s %d',
|
||||
c == WILL and 'WILL' or 'WONT', ord(c))
|
||||
else:
|
||||
self.msg('IAC %s not recognized' % `c`)
|
||||
except EOFError: # raised by self.rawq_getchar()
|
||||
pass
|
||||
self.cookedq = self.cookedq + buf
|
||||
|
||||
def rawq_getchar(self):
|
||||
"""Get next char from raw queue.
|
||||
|
||||
Block if no data is immediately available. Raise EOFError
|
||||
when connection is closed.
|
||||
|
||||
"""
|
||||
if not self.rawq:
|
||||
self.fill_rawq()
|
||||
if self.eof:
|
||||
raise EOFError
|
||||
c = self.rawq[self.irawq]
|
||||
self.irawq = self.irawq + 1
|
||||
if self.irawq >= len(self.rawq):
|
||||
self.rawq = ''
|
||||
self.irawq = 0
|
||||
return c
|
||||
|
||||
def fill_rawq(self):
|
||||
"""Fill raw queue from exactly one recv() system call.
|
||||
|
||||
Block if no data is immediately available. Set self.eof when
|
||||
connection is closed.
|
||||
|
||||
"""
|
||||
if self.irawq >= len(self.rawq):
|
||||
self.rawq = ''
|
||||
self.irawq = 0
|
||||
# The buffer size should be fairly small so as to avoid quadratic
|
||||
# behavior in process_rawq() above
|
||||
buf = self.sock.recv(50)
|
||||
self.eof = (not buf)
|
||||
self.rawq = self.rawq + buf
|
||||
|
||||
def sock_avail(self):
|
||||
"""Test whether data is available on the socket."""
|
||||
return select.select([self], [], [], 0) == ([self], [], [])
|
||||
|
||||
def interact(self):
|
||||
"""Interaction function, emulates a very dumb telnet client."""
|
||||
import sys, select
|
||||
while 1:
|
||||
rfd, wfd, xfd = select.select([self, sys.stdin], [], [])
|
||||
if sys.stdin in rfd:
|
||||
line = sys.stdin.readline()
|
||||
self.write(line)
|
||||
if self in rfd:
|
||||
try:
|
||||
text = self.read_eager()
|
||||
except EOFError:
|
||||
print '*** Connection closed by remote host ***'
|
||||
break
|
||||
if text:
|
||||
sys.stdout.write(text)
|
||||
sys.stdout.flush()
|
||||
self.close()
|
||||
|
||||
|
||||
|
||||
def test():
|
||||
"""Test program for telnetlib.
|
||||
|
||||
Usage: python telnetlib.py [-d] ... [host [port]]
|
||||
|
||||
Default host is localhost; default port is 23.
|
||||
|
||||
"""
|
||||
import sys
|
||||
debuglevel = 0
|
||||
while sys.argv[1:] and sys.argv[1] == '-d':
|
||||
debuglevel = debuglevel+1
|
||||
del sys.argv[1]
|
||||
host = 'localhost'
|
||||
if sys.argv[1:]:
|
||||
host = sys.argv[1]
|
||||
port = 0
|
||||
if sys.argv[2:]:
|
||||
portstr = sys.argv[2]
|
||||
try:
|
||||
port = int(portstr)
|
||||
except ValueError:
|
||||
port = socket.getservbyname(portstr, 'tcp')
|
||||
tn = Telnet()
|
||||
tn.set_debuglevel(debuglevel)
|
||||
tn.open(host, port)
|
||||
tn.interact()
|
||||
tn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
Loading…
Reference in New Issue