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