322 lines
9.0 KiB
Python
Executable File
322 lines
9.0 KiB
Python
Executable File
# A TELNET client class. Based on RFC 854: TELNET Protocol
|
|
# Specification, by J. Postel and J. Reynolds
|
|
|
|
|
|
# Example:
|
|
#
|
|
# >>> from telnetlib import Telnet
|
|
# >>> tn = Telnet('voorn.cwi.nl', 79) # connect to finger port
|
|
# >>> tn.write('guido\r\n')
|
|
# >>> print tn.read_all()
|
|
# Login name: guido In real life: Guido van Rossum
|
|
# Office: M353, x4127 Home phone: 020-6225521
|
|
# Directory: /ufs/guido Shell: /usr/local/bin/esh
|
|
# On since Oct 28 11:02:16 on ttyq1
|
|
# Project: Multimedia Kernel Systems
|
|
# No Plan.
|
|
# >>>
|
|
#
|
|
# Note that read() 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 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)
|
|
|
|
|
|
# Telnet interface class
|
|
|
|
class Telnet:
|
|
|
|
# Constructor
|
|
def __init__(self, host, *args):
|
|
if not args:
|
|
port = TELNET_PORT
|
|
else:
|
|
if len(args) > 1: raise TypeError, 'too many args'
|
|
port = args[0]
|
|
if not port: port = TELNET_PORT
|
|
self.debuglevel = DEBUGLEVEL
|
|
self.host = host
|
|
self.port = port
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
self.sock.connect((self.host, self.port))
|
|
self.rawq = ''
|
|
self.irawq = 0
|
|
self.cookedq = ''
|
|
self.eof = 0
|
|
|
|
# Destructor
|
|
def __del__(self):
|
|
self.close()
|
|
|
|
# Debug message
|
|
def msg(self, msg, *args):
|
|
if self.debuglevel > 0:
|
|
print 'Telnet(%s,%d):' % (self.host, self.port), msg % args
|
|
|
|
# Set debug level
|
|
def set_debuglevel(self, debuglevel):
|
|
self.debuglevel = debuglevel
|
|
|
|
# Explicit close
|
|
def close(self):
|
|
if self.sock:
|
|
self.sock.close()
|
|
self.sock = None
|
|
self.eof = 1
|
|
|
|
# Return socket (e.g. for select)
|
|
def get_socket(self):
|
|
return self.sock
|
|
|
|
# Return socket's fileno (e.g. for select)
|
|
def fileno(self):
|
|
return self.sock.fileno()
|
|
|
|
# Write a string to the socket, doubling any IAC characters
|
|
# Might block if the connection is blocked
|
|
# May raise socket.error if the connection is closed
|
|
def write(self, buffer):
|
|
if IAC in buffer:
|
|
buffer = regsub.gsub(IAC, IAC+IAC, buffer)
|
|
self.sock.send(buffer)
|
|
|
|
# The following read_* methods exist:
|
|
# Special case:
|
|
# - read_until() reads until a string is encountered or a timeout is hit
|
|
# These may block:
|
|
# - read_all() reads all data until EOF
|
|
# - read_some() reads at least one byte until EOF
|
|
# These may do I/O but won't block doing it:
|
|
# - read_very_eager() reads all data available on the socket
|
|
# - read_eager() reads either data already queued or some data
|
|
# available on the socket
|
|
# These don't do I/O:
|
|
# - read_lazy() reads all data in the raw queue (processing it first)
|
|
# - read_very_lazy() reads all data in the cooked queue
|
|
|
|
# Read until a given string is encountered or until timeout
|
|
# Raise EOFError if connection closed and no cooked data available
|
|
# Return '' if no cooked data available otherwise
|
|
def read_until(self, match, *args):
|
|
if not args:
|
|
timeout = None
|
|
else:
|
|
if len(args) > 1: raise TypeError, 'too many args'
|
|
timeout = args[0]
|
|
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()
|
|
|
|
# Read all data until EOF
|
|
# Block until connection closed
|
|
def read_all(self):
|
|
self.process_rawq()
|
|
while not self.eof:
|
|
self.fill_rawq()
|
|
self.process_rawq()
|
|
buf = self.cookedq
|
|
self.cookedq = ''
|
|
return buf
|
|
|
|
# Read at least one byte of cooked data unless EOF is hit
|
|
# Return '' if EOF is hit
|
|
# Block if no data is immediately available
|
|
def read_some(self):
|
|
self.process_rawq()
|
|
while not self.cookedq and not self.eof:
|
|
self.fill_rawq()
|
|
self.process_rawq()
|
|
buf = self.cookedq
|
|
self.cookedq = ''
|
|
return buf
|
|
|
|
# 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
|
|
def read_very_eager(self):
|
|
self.process_rawq()
|
|
while not self.eof and self.sock_avail():
|
|
self.fill_rawq()
|
|
self.process_rawq()
|
|
return self.read_very_lazy()
|
|
|
|
# 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
|
|
def read_eager(self):
|
|
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()
|
|
|
|
# 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
|
|
def read_lazy(self):
|
|
self.process_rawq()
|
|
return self.read_very_lazy()
|
|
|
|
# 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
|
|
def read_very_lazy(self):
|
|
buf = self.cookedq
|
|
self.cookedq = ''
|
|
if not buf and self.eof and not self.rawq:
|
|
raise EOFError, 'telnet connection closed'
|
|
return buf
|
|
|
|
# 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
|
|
def process_rawq(self):
|
|
buf = ''
|
|
try:
|
|
while self.rawq:
|
|
c = self.rawq_getchar()
|
|
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
|
|
|
|
# Get next char from raw queue
|
|
# Block if no data is immediately available
|
|
# Raise EOFError when connection is closed
|
|
def rawq_getchar(self):
|
|
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
|
|
|
|
# Fill raw queue from exactly one recv() system call
|
|
# Block if no data is immediately available
|
|
# Set self.eof when connection is closed
|
|
def fill_rawq(self):
|
|
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
|
|
|
|
# Test whether data is available on the socket
|
|
def sock_avail(self):
|
|
return select.select([self], [], [], 0) == ([self], [], [])
|
|
|
|
|
|
# Test program
|
|
# Usage: test [-d] ... [host [port]]
|
|
def test():
|
|
import sys, string, socket, select
|
|
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 = string.atoi(portstr)
|
|
except string.atoi_error:
|
|
port = socket.getservbyname(portstr, 'tcp')
|
|
tn = Telnet(host, port)
|
|
tn.set_debuglevel(debuglevel)
|
|
while 1:
|
|
rfd, wfd, xfd = select.select([tn, sys.stdin], [], [])
|
|
if sys.stdin in rfd:
|
|
line = sys.stdin.readline()
|
|
tn.write(line)
|
|
if tn in rfd:
|
|
try:
|
|
text = tn.read_eager()
|
|
except EOFError:
|
|
print '*** Connection closed by remote host ***'
|
|
break
|
|
if text:
|
|
sys.stdout.write(text)
|
|
sys.stdout.flush()
|
|
tn.close()
|