# 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()