import telnetsrv in contrib.
because:
- RHEL6/Fedora18 doesn't have a package of telnetsrv.
- telnetsrv 0.4 has some known bugs.
- ssh problems
fixed by telnetsrv commit 051665d79c10cd08c3600a6aa3854835c8c38fbb.
- socket/thread leaks on forcible disconnect
this has not been fixed in the upstream yet.
https://github.com/ianepperson/telnetsrvlib/pull/2
this commit imports the following version, which contains fixes of
the above mentioned bugs.
https://github.com/yamt/telnetsrvlib.git
commit 365e021479debae45c1d570e0e1c2e9ef0c2a020
Signed-off-by: YAMAMOTO Takashi <[email protected]>
---
ryu/contrib/telnetsrv/__init__.py | 1 +
ryu/contrib/telnetsrv/green.py | 56 ++
ryu/contrib/telnetsrv/paramiko_ssh.py | 239 ++++++++
ryu/contrib/telnetsrv/telnetsrvlib.py | 1045 +++++++++++++++++++++++++++++++++
ryu/contrib/telnetsrv/threaded.py | 89 +++
5 files changed, 1430 insertions(+)
create mode 100644 ryu/contrib/telnetsrv/__init__.py
create mode 100644 ryu/contrib/telnetsrv/green.py
create mode 100644 ryu/contrib/telnetsrv/paramiko_ssh.py
create mode 100755 ryu/contrib/telnetsrv/telnetsrvlib.py
create mode 100755 ryu/contrib/telnetsrv/threaded.py
diff --git a/ryu/contrib/telnetsrv/__init__.py
b/ryu/contrib/telnetsrv/__init__.py
new file mode 100644
index 0000000..b680692
--- /dev/null
+++ b/ryu/contrib/telnetsrv/__init__.py
@@ -0,0 +1 @@
+__all__ = []
\ No newline at end of file
diff --git a/ryu/contrib/telnetsrv/green.py b/ryu/contrib/telnetsrv/green.py
new file mode 100644
index 0000000..c5d79a8
--- /dev/null
+++ b/ryu/contrib/telnetsrv/green.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+# Telnet handler concrete class using green threads
+
+import gevent, gevent.queue
+
+from telnetsrvlib import TelnetHandlerBase, command
+
+class TelnetHandler(TelnetHandlerBase):
+ "A telnet server handler using Gevent"
+ def __init__(self, request, client_address, server):
+ # Create a green queue for input handling
+ self.cookedq = gevent.queue.Queue()
+ # Call the base class init method
+ TelnetHandlerBase.__init__(self, request, client_address, server)
+
+ def setup(self):
+ '''Called after instantiation'''
+ TelnetHandlerBase.setup(self)
+ # Spawn a greenlet to handle socket input
+ self.greenlet_ic = gevent.spawn(self.inputcooker)
+ # Note that inputcooker exits on EOF
+
+ # Sleep for 0.5 second to allow options negotiation
+ gevent.sleep(0.5)
+
+ def finish(self):
+ '''Called as the session is ending'''
+ TelnetHandlerBase.finish(self)
+ # Ensure the greenlet is dead
+ self.greenlet_ic.kill()
+
+
+ # -- Green input handling functions --
+
+ def getc(self, block=True):
+ """Return one character from the input queue"""
+ while True:
+ try:
+ return self.cookedq.get(block, timeout = 10)
+ except gevent.queue.Empty:
+ if block and not self.eof:
+ continue
+ return ''
+
+ def inputcooker_socket_ready(self):
+ """Indicate that the socket is ready to be read"""
+ return gevent.select.select([self.sock.fileno()], [], [], 0) != ([],
[], [])
+
+ def inputcooker_store_queue(self, char):
+ """Put the cooked data in the input queue (no locking needed)"""
+ if type(char) in [type(()), type([]), type("")]:
+ for v in char:
+ self.cookedq.put(v)
+ else:
+ self.cookedq.put(char)
+
diff --git a/ryu/contrib/telnetsrv/paramiko_ssh.py
b/ryu/contrib/telnetsrv/paramiko_ssh.py
new file mode 100644
index 0000000..c5a8b72
--- /dev/null
+++ b/ryu/contrib/telnetsrv/paramiko_ssh.py
@@ -0,0 +1,239 @@
+import logging
+#from binascii import hexlify
+from threading import Thread
+from SocketServer import BaseRequestHandler
+
+from paramiko import Transport, ServerInterface, RSAKey, DSSKey, SSHException,
\
+ AUTH_SUCCESSFUL, AUTH_FAILED, \
+ OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, \
+ OPEN_FAILED_UNKNOWN_CHANNEL_TYPE,
OPEN_FAILED_RESOURCE_SHORTAGE
+
+
+log = logging.getLogger(__name__)
+
+def getRsaKeyFile(filename, password=None):
+ try:
+ key = RSAKey(filename=filename, password=password)
+ except IOError:
+ log.info('Generating new server RSA key and saving in file %r.' %
filename)
+ key = RSAKey.generate(1024)
+ key.write_private_key_file(filename, password=password)
+ return key
+
+
+class TelnetToPtyHandler(object):
+ '''Mixin to turn TelnetHandler into PtyHandler'''
+ def __init__(self, *args):
+ super(TelnetToPtyHandler, self).__init__(*args)
+
+ # Don't mention these, client isn't listening for them. Blank the dicts.
+ DOACK = {}
+ WILLACK = {}
+
+ # Do not ask for auth in the PTY, it'll be handled via SSH, then passed in
with the request
+ def authentication_ok(self):
+ '''Checks the authentication and sets the username of the currently
connected terminal. Returns True or False'''
+ # Since authentication already happened, this should always return true
+ self.username = self.request.username
+ return True
+
+
+class SSHHandler(ServerInterface, BaseRequestHandler):
+ telnet_handler = None
+ pty_handler = None
+ host_key = None
+ username = None
+
+ def __init__(self, request, client_address, server):
+ self.request = request
+ self.client_address = client_address
+ self.tcp_server = server
+
+ # Keep track of channel information from the transport
+ self.channels = {}
+
+ self.client = request._sock
+ # Transport turns the socket into an SSH transport
+ self.transport = Transport(self.client)
+
+ # Create the PTY handler class by mixing in
+ TelnetHandlerClass = self.telnet_handler
+ class MixedPtyHandler(TelnetToPtyHandler, TelnetHandlerClass):
+ # BaseRequestHandler does not inherit from object, must call the
__init__ directly
+ def __init__(self, *args):
+ TelnetHandlerClass.__init__(self, *args)
+ self.pty_handler = MixedPtyHandler
+
+
+ # Call the base class to run the handler
+ BaseRequestHandler.__init__(self, request, client_address, server)
+
+ def setup(self):
+ '''Setup the connection.'''
+ log.debug( 'New request from address %s, port %d',
self.client_address )
+
+ try:
+ self.transport.load_server_moduli()
+ except:
+ log.exception( '(Failed to load moduli -- gex will be
unsupported.)' )
+ raise
+ try:
+ self.transport.add_server_key(self.host_key)
+ except:
+ if self.host_key is None:
+ log.critical('Host key not set! SSHHandler MUST define the
host_key parameter.')
+ raise NotImplementedError('Host key not set! SSHHandler
instance must define the host_key parameter. Try host_key =
paramiko_ssh.getRsaKeyFile("server_rsa.key").')
+
+ try:
+ # Tell transport to use this object as a server
+ log.debug( 'Starting SSH server-side negotiation' )
+ self.transport.start_server(server=self)
+ except SSHException, e:
+ log.warn('SSH negotiation failed. %s', e)
+ raise
+
+ # Accept any requested channels
+ while True:
+ channel = self.transport.accept(20)
+ if channel is None:
+ # check to see if any thread is running
+ any_running = False
+ for c, thread in self.channels.items():
+ if thread.is_alive():
+ any_running = True
+ break
+ if not any_running:
+ break
+ else:
+ log.info( 'Accepted channel %s', channel )
+ #raise RuntimeError('No channel requested.')
+
+
+
+ class dummy_request(object):
+ def __init__(self):
+ self._sock = None
+
+ @classmethod
+ def streamserver_handle(cls, socket, address):
+ '''Translate this class for use in a StreamServer'''
+ request = cls.dummy_request()
+ request._sock = socket
+ server = None
+ cls(request, address, server)
+
+
+ def finish(self):
+ '''Called when the socket closes from the client.'''
+ self.transport.close()
+
+
+ def check_channel_request(self, kind, chanid):
+ if kind == 'session':
+ return OPEN_SUCCEEDED
+ return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
+
+ def set_username(self, username):
+ self.username = username
+ log.info('User logged in: %s' % username)
+
+ ###### Handle User Authentication ######
+
+ # Override these with functions to use for callbacks
+ authCallback = None
+ authCallbackKey = None
+ authCallbackUsername = None
+
+ def get_allowed_auths(self, username):
+ methods = []
+ if self.authCallbackUsername is not None:
+ methods.append('none')
+ if self.authCallback is not None:
+ methods.append('password')
+ if self.authCallbackKey is not None:
+ methods.append('publickey')
+
+ if methods == []:
+ # If no methods were defined, use none
+ methods.append('none')
+
+ log.debug('Configured authentication methods: %r', methods)
+ return ','.join(methods)
+
+ def check_auth_password(self, username, password):
+ #print 'check_auth_password(%s, %s)' % (username, password)
+ try:
+ self.authCallback(username, password)
+ except:
+ return AUTH_FAILED
+ else:
+ self.set_username(username)
+ return AUTH_SUCCESSFUL
+
+
+ def check_auth_publickey(self, username, key):
+ #print 'Auth attempt with key: ' + hexlify(key.get_fingerprint())
+ try:
+ self.authCallbackKey(username, key)
+ except:
+ return AUTH_FAILED
+ else:
+ self.set_username(username)
+ return AUTH_SUCCESSFUL
+ #if (username == 'xx') and (key == self.good_pub_key):
+ # return AUTH_SUCCESSFUL
+
+
+ def check_auth_none(self, username):
+ if self.authCallbackUsername is None:
+ self.set_username(username)
+ return AUTH_SUCCESSFUL
+ try:
+ self.authCallbackUsername(username)
+ except:
+ return AUTH_FAILED
+ else:
+ self.set_username(username)
+ return AUTH_SUCCESSFUL
+
+
+ def check_channel_shell_request(self, channel):
+ '''Request to start a shell on the given channel'''
+ try:
+ self.channels[channel].start()
+ except KeyError:
+ log.error('Requested to start a channel (%r) that was not
previously set up.', channel)
+ return False
+ else:
+ return True
+
+ def check_channel_pty_request(self, channel, term, width, height,
pixelwidth,
+ pixelheight, modes):
+ '''Request to allocate a PTY terminal.'''
+ #self.sshterm = term
+ #print "term: %r, modes: %r" % (term, modes)
+ log.debug('PTY requested. Setting up %r.', self.telnet_handler)
+ pty_thread = Thread( target=self.start_pty_request, args=(channel,
term, modes) )
+ self.channels[channel] = pty_thread
+
+ return True
+
+ def start_pty_request(self, channel, term, modes):
+ '''Start a PTY - intended to run it a (green)thread.'''
+ request = self.dummy_request()
+ request._sock = channel
+ request.modes = modes
+ request.term = term
+ request.username = self.username
+
+ # modes = http://www.ietf.org/rfc/rfc4254.txt page 18
+ # for i in xrange(50):
+ # print "%r: %r" % (int(m[i*5].encode('hex'), 16),
int(''.join(m[i*5+1:i*5+5]).encode('hex'), 16))
+
+
+ # This should block until the user quits the pty
+ self.pty_handler(request, self.client_address, self.tcp_server)
+
+ # Shutdown the entire session
+ self.transport.close()
+
diff --git a/ryu/contrib/telnetsrv/telnetsrvlib.py
b/ryu/contrib/telnetsrv/telnetsrvlib.py
new file mode 100755
index 0000000..148d727
--- /dev/null
+++ b/ryu/contrib/telnetsrv/telnetsrvlib.py
@@ -0,0 +1,1045 @@
+# license: LGPL
+# For distribution, see the COPYING.txt file that accompanies this file.
+"""TELNET server class
+
+Based on the telnet client in telnetlib.py
+
+Presents a command line interface to the telnet client.
+Various settings can affect the operation of the server:
+
+ authCallback = Reference to authentication function. If
+ there is none, no un/pw is requested. Should
+ raise an exception if authentication fails
+ Default: None
+ authNeedUser = Should a username be requested?
+ Default: False
+ authNeedPass = Should a password be requested?
+ Default: False
+ COMMANDS = Dictionary of supported commands
+ Key = command (Must be upper case)
+ Value = List of (function, help text)
+ Function.__doc__ should be long help
+ Function.aliases may be a list of alternative spellings
+"""
+
+import SocketServer
+import socket
+import sys
+import traceback
+import curses.ascii
+import curses.has_key
+import curses
+import logging
+import re
+#if not hasattr(socket, 'SHUT_RDWR'):
+# socket.SHUT_RDWR = 2
+
+log = logging.getLogger(__name__)
+
+BELL = chr(7)
+ESC = chr(27)
+ANSI_START_SEQ = '['
+ANSI_KEY_TO_CURSES = {
+ 'A': curses.KEY_UP,
+ 'B': curses.KEY_DOWN,
+ 'C': curses.KEY_RIGHT,
+ 'D': curses.KEY_LEFT,
+ }
+
+IAC = chr(255) # "Interpret As Command"
+DONT = chr(254)
+DO = chr(253)
+WONT = chr(252)
+WILL = chr(251)
+theNULL = chr(0)
+
+SE = chr(240) # Subnegotiation End
+NOP = chr(241) # No Operation
+DM = chr(242) # Data Mark
+BRK = chr(243) # Break
+IP = chr(244) # Interrupt process
+AO = chr(245) # Abort output
+AYT = chr(246) # Are You There
+EC = chr(247) # Erase Character
+EL = chr(248) # Erase Line
+GA = chr(249) # Go Ahead
+SB = chr(250) # Subnegotiation Begin
+
+BINARY = chr(0) # 8-bit data path
+ECHO = chr(1) # echo
+RCP = chr(2) # prepare to reconnect
+SGA = chr(3) # suppress go ahead
+NAMS = chr(4) # approximate message size
+STATUS = chr(5) # give status
+TM = chr(6) # timing mark
+RCTE = chr(7) # remote controlled transmission and echo
+NAOL = chr(8) # negotiate about output line width
+NAOP = chr(9) # negotiate about output page size
+NAOCRD = chr(10) # negotiate about CR disposition
+NAOHTS = chr(11) # negotiate about horizontal tabstops
+NAOHTD = chr(12) # negotiate about horizontal tab disposition
+NAOFFD = chr(13) # negotiate about formfeed disposition
+NAOVTS = chr(14) # negotiate about vertical tab stops
+NAOVTD = chr(15) # negotiate about vertical tab disposition
+NAOLFD = chr(16) # negotiate about output LF disposition
+XASCII = chr(17) # extended ascii character set
+LOGOUT = chr(18) # force logout
+BM = chr(19) # byte macro
+DET = chr(20) # data entry terminal
+SUPDUP = chr(21) # supdup protocol
+SUPDUPOUTPUT = chr(22) # supdup output
+SNDLOC = chr(23) # send location
+TTYPE = chr(24) # terminal type
+EOR = chr(25) # end or record
+TUID = chr(26) # TACACS user identification
+OUTMRK = chr(27) # output marking
+TTYLOC = chr(28) # terminal location number
+VT3270REGIME = chr(29) # 3270 regime
+X3PAD = chr(30) # X.3 PAD
+NAWS = chr(31) # window size
+TSPEED = chr(32) # terminal speed
+LFLOW = chr(33) # remote flow control
+LINEMODE = chr(34) # Linemode option
+XDISPLOC = chr(35) # X Display Location
+OLD_ENVIRON = chr(36) # Old - Environment variables
+AUTHENTICATION = chr(37) # Authenticate
+ENCRYPT = chr(38) # Encryption option
+NEW_ENVIRON = chr(39) # New - Environment variables
+# the following ones come from
+# http://www.iana.org/assignments/telnet-options
+# Unfortunately, that document does not assign identifiers
+# to all of them, so we are making them up
+TN3270E = chr(40) # TN3270E
+XAUTH = chr(41) # XAUTH
+CHARSET = chr(42) # CHARSET
+RSP = chr(43) # Telnet Remote Serial Port
+COM_PORT_OPTION = chr(44) # Com Port Control Option
+SUPPRESS_LOCAL_ECHO = chr(45) # Telnet Suppress Local Echo
+TLS = chr(46) # Telnet Start TLS
+KERMIT = chr(47) # KERMIT
+SEND_URL = chr(48) # SEND-URL
+FORWARD_X = chr(49) # FORWARD_X
+PRAGMA_LOGON = chr(138) # TELOPT PRAGMA LOGON
+SSPI_LOGON = chr(139) # TELOPT SSPI LOGON
+PRAGMA_HEARTBEAT = chr(140) # TELOPT PRAGMA HEARTBEAT
+EXOPL = chr(255) # Extended-Options-List
+NOOPT = chr(0)
+
+#Codes used in SB SE data stream for terminal type negotiation
+IS = chr(0)
+SEND = chr(1)
+
+CMDS = {
+ WILL: 'WILL',
+ WONT: 'WONT',
+ DO: 'DO',
+ DONT: 'DONT',
+ SE: 'Subnegotiation End',
+ NOP: 'No Operation',
+ DM: 'Data Mark',
+ BRK: 'Break',
+ IP: 'Interrupt process',
+ AO: 'Abort output',
+ AYT: 'Are You There',
+ EC: 'Erase Character',
+ EL: 'Erase Line',
+ GA: 'Go Ahead',
+ SB: 'Subnegotiation Begin',
+ BINARY: 'Binary',
+ ECHO: 'Echo',
+ RCP: 'Prepare to reconnect',
+ SGA: 'Suppress Go-Ahead',
+ NAMS: 'Approximate message size',
+ STATUS: 'Give status',
+ TM: 'Timing mark',
+ RCTE: 'Remote controlled transmission and echo',
+ NAOL: 'Negotiate about output line width',
+ NAOP: 'Negotiate about output page size',
+ NAOCRD: 'Negotiate about CR disposition',
+ NAOHTS: 'Negotiate about horizontal tabstops',
+ NAOHTD: 'Negotiate about horizontal tab disposition',
+ NAOFFD: 'Negotiate about formfeed disposition',
+ NAOVTS: 'Negotiate about vertical tab stops',
+ NAOVTD: 'Negotiate about vertical tab disposition',
+ NAOLFD: 'Negotiate about output LF disposition',
+ XASCII: 'Extended ascii character set',
+ LOGOUT: 'Force logout',
+ BM: 'Byte macro',
+ DET: 'Data entry terminal',
+ SUPDUP: 'Supdup protocol',
+ SUPDUPOUTPUT: 'Supdup output',
+ SNDLOC: 'Send location',
+ TTYPE: 'Terminal type',
+ EOR: 'End or record',
+ TUID: 'TACACS user identification',
+ OUTMRK: 'Output marking',
+ TTYLOC: 'Terminal location number',
+ VT3270REGIME: '3270 regime',
+ X3PAD: 'X.3 PAD',
+ NAWS: 'Window size',
+ TSPEED: 'Terminal speed',
+ LFLOW: 'Remote flow control',
+ LINEMODE: 'Linemode option',
+ XDISPLOC: 'X Display Location',
+ OLD_ENVIRON: 'Old - Environment variables',
+ AUTHENTICATION: 'Authenticate',
+ ENCRYPT: 'Encryption option',
+ NEW_ENVIRON: 'New - Environment variables',
+}
+
+
+
+class command():
+ '''Function decorator to define a telnet command.'''
+ def __init__(self, names, hidden=False):
+ if type(names) is str:
+ self.name = names
+ self.alias = []
+ else:
+ self.name = names[0]
+ self.alias = names[1:]
+ self.hidden = hidden
+
+ def __call__(self, fn):
+ try:
+ # First, assume there are more than one decorators.
+ # Try to prepend to the list of aliases.
+ fn.aliases.append(fn.command_name)
+ fn.aliases.extend(self.alias)
+ fn.command_name = self.name
+ fn.hidden = self.hidden or fn.hidden
+ except:
+ # If that didn't work, this method only has one decorator
+ fn.aliases = self.alias
+ fn.command_name = self.name
+ fn.hidden = self.hidden
+ return fn
+
+
+
+class InputSimple(object):
+ '''Simple line handler. All spaces become one, can have quoted
parameters, but not null'''
+ quote_chars = ['"', "'"]
+ def __init__(self, handler, line):
+ self.parts = []
+ self.process(line)
+
+ @property
+ def cmd(self):
+ try:
+ return self.parts[0]
+ except IndexError:
+ return ''
+
+ @property
+ def params(self):
+ return self.parts[1:]
+
+
+ def process(self, line):
+ line = line.strip()
+ self.raw = line
+ cmdlist = [item.strip() for item in line.split()]
+ idx = 0
+ while idx < (len(cmdlist) - 1):
+ if cmdlist[idx][0] in ["'", '"']:
+ cmdlist[idx] = cmdlist[idx] + " " + cmdlist.pop(idx+1)
+ if cmdlist[idx][0] != cmdlist[idx][-1]:
+ continue
+ cmdlist[idx] = cmdlist[idx][1:-1]
+ idx = idx + 1
+ self.parts = cmdlist
+
+
+class InputBashLike(object):
+ '''Handles escaped characters, quoted parameters and multi-line input
similar to Bash.'''
+ quote_chars = ['"', "'"]
+ whitespace = [' ', '\t']
+ escape_char = "\\"
+ escape_results = {'\\':'\\', 't':'\t', 'n':'\n', ' ':' ', '"': '"',
"'":"'"}
+ continue_prompt = '... '
+ eol_char = '\n'
+
+ def __init__(self, handler, line):
+ self.raw = ''
+ self.handler = handler
+ self.complete = False
+ self.inquote = False
+ self.parts = []
+ self.part = []
+ # Set up the initial processing state.
+ self.process_char = self.process_delimiter
+ self.process(line)
+
+ @property
+ def cmd(self):
+ try:
+ return self.parts[0]
+ except IndexError:
+ return ''
+
+ @property
+ def params(self):
+ return self.parts[1:]
+
+ # The following process_x functions handle different states while stepping
through the chars of the line.
+
+ def process_delimiter(self, char):
+ '''Process chars while not in a part'''
+ if char in self.whitespace:
+ return
+ if char in self.quote_chars:
+ # Store the quote type (' or ") and switch to quote processing.
+ self.inquote = char
+ self.process_char = self.process_quote
+ return
+ if char == self.eol_char:
+ self.complete = True
+ return
+ # Switch to processing a part.
+ self.process_char = self.process_part
+ self.process_char(char)
+
+ def process_part(self, char):
+ '''Process chars while in a part'''
+ if char in self.whitespace or char == self.eol_char:
+ # End of the part.
+ self.parts.append( ''.join(self.part) )
+ self.part = []
+ # Switch back to processing a delimiter.
+ self.process_char = self.process_delimiter
+ if char == self.eol_char:
+ self.complete = True
+ return
+ if char in self.quote_chars:
+ # Store the quote type (' or ") and switch to quote processing.
+ self.inquote = char
+ self.process_char = self.process_quote
+ return
+ self.part.append(char)
+
+ def process_quote(self, char):
+ '''Process character while in a quote'''
+ if char == self.inquote:
+ # Quote is finished, switch to part processing.
+ self.process_char = self.process_part
+ return
+ try:
+ self.part.append(char)
+ except:
+ self.part = [ char ]
+
+ def process_escape(self, char):
+ '''Handle the char after the escape char'''
+ # Always only run once, switch back to the last processor.
+ self.process_char = self.last_process_char
+ if self.part == [] and char in self.whitespace:
+ # Special case where \ is by itself and not at the EOL.
+ self.parts.append(self.escape_char)
+ return
+ if char == self.eol_char:
+ # Ignore a cr.
+ return
+ unescaped = self.escape_results.get(char, self.escape_char+char)
+ self.part.append(unescaped)
+
+
+ def process(self, line):
+ '''Step through the line and process each character'''
+ self.raw = self.raw + line
+ try:
+ if not line[-1] == self.eol_char:
+ # Should always be here, but add it just in case.
+ line = line + self.eol_char
+ except IndexError:
+ # Thrown if line == ''
+ line = self.eol_char
+
+ for char in line:
+ if char == self.escape_char:
+ # Always handle escaped characters.
+ self.last_process_char = self.process_char
+ self.process_char = self.process_escape
+ continue
+ self.process_char(char)
+ if not self.complete:
+ # Ask for more.
+ self.process(
self.handler.readline(prompt=self.handler.CONTINUE_PROMPT) )
+
+
+class TelnetHandlerBase(SocketServer.BaseRequestHandler):
+ "A telnet server based on the client in telnetlib"
+
+ # Several methods are not fully defined in this class, and are
+ # very specific to either a threaded or green implementation.
+ # These methods are noted as #abstracmethods to ensure they are
+ # properly made concrete.
+ # (abc doesn't like the BaseRequestHandler - sigh)
+ #__metaclass__ = ABCMeta
+
+ # What I am prepared to do?
+ DOACK = {
+ ECHO: WILL,
+ SGA: WILL,
+ NEW_ENVIRON: WONT,
+ }
+ # What do I want the client to do?
+ WILLACK = {
+ ECHO: DONT,
+ SGA: DO,
+ NAWS: DONT,
+ TTYPE: DO,
+ LINEMODE: DONT,
+ NEW_ENVIRON: DO,
+ }
+ # Default terminal type - used if client doesn't tell us its termtype
+ TERM = "ansi"
+ # Keycode to name mapping - used to decide which keys to query
+ KEYS = { # Key escape sequences
+ curses.KEY_UP: 'Up', # Cursor up
+ curses.KEY_DOWN: 'Down', # Cursor down
+ curses.KEY_LEFT: 'Left', # Cursor left
+ curses.KEY_RIGHT: 'Right', # Cursor right
+ curses.KEY_DC: 'Delete', # Delete right
+ curses.KEY_BACKSPACE: 'Backspace', # Delete left
+ }
+ # Reverse mapping of KEYS - used for cooking key codes
+ ESCSEQ = {
+ }
+ # Terminal output escape sequences
+ CODES = {
+ 'DEOL': '', # Delete to end of line
+ 'DEL': '', # Delete and close up
+ 'INS': '', # Insert space
+ 'CSRLEFT': '', # Move cursor left 1 space
+ 'CSRRIGHT': '', # Move cursor right 1 space
+ }
+ # What prompt to display
+ PROMPT = "Telnet Server> "
+ # What prompt to use for requesting more input
+ CONTINUE_PROMPT = "... "
+ # What to display upon connection
+ WELCOME = "You have connected to the telnet server."
+ # The function to call to verify authentication data
+ authCallback = None
+ # Does authCallback want a username?
+ authNeedUser = False
+ # Does authCallback want a password?
+ authNeedPass = False
+ # Default username
+ username = None
+ # What will handle our inputs?
+ #input_reader = InputSimple
+ input_reader = InputBashLike
+
+# --------------------------- Environment Setup ----------------------------
+
+ def __init__(self, request, client_address, server):
+ """Constructor.
+
+ When called without arguments, create an unconnected instance.
+ With a hostname argument, it connects the instance; a port
+ number is optional.
+ """
+ # Am I doing the echoing?
+ self.DOECHO = True
+ # What opts have I sent DO/DONT for and what did I send?
+ self.DOOPTS = {}
+ # What opts have I sent WILL/WONT for and what did I send?
+ self.WILLOPTS = {}
+ # What commands does this CLI support
+ self.COMMANDS = {}
+ self.sock = None # TCP socket
+ self.rawq = '' # Raw input string
+ self.sbdataq = '' # Sub-Neg string
+ self.eof = 0 # Has EOF been reached?
+ self.iacseq = '' # Buffer for IAC sequence.
+ self.sb = 0 # Flag for SB and SE sequence.
+ self.history = [] # Command history
+ self.RUNSHELL = True
+ # A little magic - Everything called cmdXXX is a command
+ # Also, check for decorated functions
+ for k in dir(self):
+ method = getattr(self, k)
+ try:
+ name = method.command_name
+ except:
+ if k[:3] == 'cmd':
+ name = k[3:]
+ else:
+ continue
+
+ name = name.upper()
+ self.COMMANDS[name] = method
+ for alias in getattr(method, "aliases", []):
+ self.COMMANDS[alias.upper()] = self.COMMANDS[name]
+
+ SocketServer.BaseRequestHandler.__init__(self, request,
client_address, server)
+
+ class false_request(object):
+ def __init__(self):
+ self.sock = None
+
+ @classmethod
+ def streamserver_handle(cls, socket, address):
+ '''Translate this class for use in a StreamServer'''
+ request = cls.false_request()
+ request._sock = socket
+ server = None
+ log.debug("Accepted connection, starting telnet session.")
+ cls(request, address, server)
+
+ def setterm(self, term):
+ "Set the curses structures for this terminal"
+ log.debug("Setting termtype to %s" % (term, ))
+ curses.setupterm(term) # This will raise if the termtype is not
supported
+ self.TERM = term
+ self.ESCSEQ = {}
+ for k in self.KEYS.keys():
+ str = curses.tigetstr(curses.has_key._capability_names[k])
+ if str:
+ self.ESCSEQ[str] = k
+ # Create a copy to prevent altering the class
+ self.CODES = self.CODES.copy()
+ self.CODES['DEOL'] = curses.tigetstr('el')
+ self.CODES['DEL'] = curses.tigetstr('dch1')
+ self.CODES['INS'] = curses.tigetstr('ich1')
+ self.CODES['CSRLEFT'] = curses.tigetstr('cub1')
+ self.CODES['CSRRIGHT'] = curses.tigetstr('cuf1')
+
+ def setup(self):
+ "Connect incoming connection to a telnet session"
+ try:
+ self.TERM = self.request.term
+ except:
+ pass
+ self.setterm(self.TERM)
+ self.sock = self.request._sock
+ for k in self.DOACK.keys():
+ self.sendcommand(self.DOACK[k], k)
+ for k in self.WILLACK.keys():
+ self.sendcommand(self.WILLACK[k], k)
+
+
+ def finish(self):
+ "End this session"
+ log.debug("Session disconnected.")
+ try:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except socket.error as e:
+ # on some systems shutdown fails with EINVAL.
+ # http://www.freebsd.org/cgi/query-pr.cgi?pr=31647
+ import errno
+ if e.errno == errno.EINVAL:
+ pass
+ else:
+ raise
+ self.sock.close()
+ self.session_end()
+
+ def session_start(self):
+ pass
+
+ def session_end(self):
+ pass
+
+# ------------------------- Telnet Options Engine --------------------------
+
+ def options_handler(self, sock, cmd, opt):
+ "Negotiate options"
+ if cmd == NOP:
+ self.sendcommand(NOP)
+ elif cmd == WILL or cmd == WONT:
+ if self.WILLACK.has_key(opt):
+ self.sendcommand(self.WILLACK[opt], opt)
+ else:
+ self.sendcommand(DONT, opt)
+ if cmd == WILL and opt == TTYPE:
+ self.writecooked(IAC + SB + TTYPE + SEND + IAC + SE)
+ elif cmd == DO or cmd == DONT:
+ if self.DOACK.has_key(opt):
+ self.sendcommand(self.DOACK[opt], opt)
+ else:
+ self.sendcommand(WONT, opt)
+ if opt == ECHO:
+ self.DOECHO = (cmd == DO)
+ elif cmd == SE:
+ subreq = self.read_sb_data()
+ if subreq[0] == TTYPE and subreq[1] == IS:
+ try:
+ self.setterm(subreq[2:])
+ except:
+ log.debug("Terminal type not known")
+ elif cmd == SB:
+ pass
+ else:
+ log.debug("Unhandled option: %s %s" % (cmdtxt, opttxt, ))
+
+ def sendcommand(self, cmd, opt=None):
+ "Send a telnet command (IAC)"
+ if cmd in [DO, DONT]:
+ if not self.DOOPTS.has_key(opt):
+ self.DOOPTS[opt] = None
+ if (((cmd == DO) and (self.DOOPTS[opt] != True))
+ or ((cmd == DONT) and (self.DOOPTS[opt] != False))):
+ self.DOOPTS[opt] = (cmd == DO)
+ self.writecooked(IAC + cmd + opt)
+ elif cmd in [WILL, WONT]:
+ if not self.WILLOPTS.has_key(opt):
+ self.WILLOPTS[opt] = ''
+ if (((cmd == WILL) and (self.WILLOPTS[opt] != True))
+ or ((cmd == WONT) and (self.WILLOPTS[opt] != False))):
+ self.WILLOPTS[opt] = (cmd == WILL)
+ self.writecooked(IAC + cmd + opt)
+ else:
+ self.writecooked(IAC + cmd)
+
+ def read_sb_data(self):
+ """Return any data available in the SB ... SE queue.
+
+ Return '' if no SB ... SE available. Should only be called
+ after seeing a SB or SE command. When a new SB command is
+ found, old unread SB data will be discarded. Don't block.
+
+ """
+ buf = self.sbdataq
+ self.sbdataq = ''
+ return buf
+
+# ---------------------------- Input Functions -----------------------------
+
+ def _readline_do_echo(self, echo):
+ """Determine if we should echo or not"""
+ return echo == True or (echo == None and self.DOECHO == True)
+
+ def _readline_echo(self, char, echo):
+ """Echo a recieved character, move cursor etc..."""
+ if self._readline_do_echo(echo):
+ self.write(char)
+
+ def _readline_insert(self, char, echo, insptr, line):
+ """Deal properly with inserted chars in a line."""
+ if not self._readline_do_echo(echo):
+ return
+ # Write out the remainder of the line
+ self.write(char + ''.join(line[insptr:]))
+ # Cursor Left to the current insert point
+ char_count = len(line) - insptr
+ self.write(self.CODES['CSRLEFT'] * char_count)
+
+ _current_line = ''
+ _current_prompt = ''
+
+ def ansi_to_curses(self, char):
+ '''Handles reading ANSI escape sequences'''
+ # ANSI sequences are:
+ # ESC [ <key>
+ # If we see ESC, read a char
+ if char != ESC:
+ return char
+ # If we see [, read another char
+ if self.getc(block=True) != ANSI_START_SEQ:
+ self._readline_echo(BELL, True)
+ return theNULL
+ key = self.getc(block=True)
+ # Translate the key to curses
+ try:
+ return ANSI_KEY_TO_CURSES[key]
+ except:
+ self._readline_echo(BELL, True)
+ return theNULL
+
+ def readline(self, echo=None, prompt='', use_history=True):
+ """Return a line of text, including the terminating LF
+ If echo is true always echo, if echo is false never echo
+ If echo is None follow the negotiated setting.
+ prompt is the current prompt to write (and rewrite if needed)
+ use_history controls if this current line uses (and adds to) the
command history.
+ """
+
+ line = []
+ insptr = 0
+ ansi = 0
+ histptr = len(self.history)
+
+ if self.DOECHO:
+ self.write(prompt)
+ self._current_prompt = prompt
+ else:
+ self._current_prompt = ''
+
+ self._current_line = ''
+
+ while True:
+ c = self.getc(block=True)
+ c = self.ansi_to_curses(c)
+ if c == theNULL:
+ continue
+ elif c == '':
+ return 'QUIT'
+ elif c == curses.KEY_LEFT:
+ if insptr > 0:
+ insptr = insptr - 1
+ self._readline_echo(self.CODES['CSRLEFT'], echo)
+ else:
+ self._readline_echo(BELL, echo)
+ continue
+ elif c == curses.KEY_RIGHT:
+ if insptr < len(line):
+ insptr = insptr + 1
+ self._readline_echo(self.CODES['CSRRIGHT'], echo)
+ else:
+ self._readline_echo(BELL, echo)
+ continue
+ elif c == curses.KEY_UP or c == curses.KEY_DOWN:
+ if not use_history:
+ self._readline_echo(BELL, echo)
+ continue
+ if c == curses.KEY_UP:
+ if histptr > 0:
+ histptr = histptr - 1
+ else:
+ self._readline_echo(BELL, echo)
+ continue
+ elif c == curses.KEY_DOWN:
+ if histptr < len(self.history):
+ histptr = histptr + 1
+ else:
+ self._readline_echo(BELL, echo)
+ continue
+ line = []
+ if histptr < len(self.history):
+ line.extend(self.history[histptr])
+ for char in range(insptr):
+ self._readline_echo(self.CODES['CSRLEFT'], echo)
+ self._readline_echo(self.CODES['DEOL'], echo)
+ self._readline_echo(''.join(line), echo)
+ insptr = len(line)
+ continue
+ elif c == chr(3):
+ self._readline_echo('\n' + curses.ascii.unctrl(c) + '
ABORT\n', echo)
+ return ''
+ elif c == chr(4):
+ if len(line) > 0:
+ self._readline_echo('\n' + curses.ascii.unctrl(c) + '
ABORT (QUIT)\n', echo)
+ return ''
+ self._readline_echo('\n' + curses.ascii.unctrl(c) + ' QUIT\n',
echo)
+ return 'QUIT'
+ elif c == chr(10):
+ self._readline_echo(c, echo)
+ result = ''.join(line)
+ if use_history:
+ self.history.append(result)
+ if echo is False:
+ if prompt:
+ self.write( chr(10) )
+ log.debug('readline: %s(hidden text)', prompt)
+ else:
+ log.debug('readline: %s%r', prompt, result)
+ return result
+ elif c == curses.KEY_BACKSPACE or c == chr(127) or c == chr(8):
+ if insptr > 0:
+ self._readline_echo(self.CODES['CSRLEFT'] +
self.CODES['DEL'], echo)
+ insptr = insptr - 1
+ del line[insptr]
+ else:
+ self._readline_echo(BELL, echo)
+ continue
+ elif c == curses.KEY_DC:
+ if insptr < len(line):
+ self._readline_echo(self.CODES['DEL'], echo)
+ del line[insptr]
+ else:
+ self._readline_echo(BELL, echo)
+ continue
+ else:
+ if ord(c) < 32:
+ c = curses.ascii.unctrl(c)
+ if len(line) > insptr:
+ self._readline_insert(c, echo, insptr, line)
+ else:
+ self._readline_echo(c, echo)
+ line[insptr:insptr] = c
+ insptr = insptr + len(c)
+ if self._readline_do_echo(echo):
+ self._current_line = line
+
+ #abstractmethod
+ def getc(self, block=True):
+ """Return one character from the input queue"""
+ # This is very different between green threads and real threads.
+ raise NotImplementedError("Please Implement the getc method")
+
+# --------------------------- Output Functions -----------------------------
+
+ def writeresponse(self, text):
+ """Write out any valid responses. Easy to override with ANSI codes."""
+ self.writeline(text)
+
+ def writeerror(self, text):
+ """Write out any error messages. Easy to override with ANSI codes."""
+ self.writeline(text)
+
+ def writeline(self, text):
+ """Send a packet with line ending."""
+ log.debug('writing line %r' % text)
+ self.write(text+chr(10))
+
+ def writemessage(self, text):
+ """Write out an asynchronous message, then reconstruct the prompt and
entered text."""
+ log.debug('writing message %r', text)
+ self.write(chr(10)+text+chr(10))
+ self.write(self._current_prompt+''.join(self._current_line))
+
+ def write(self, text):
+ """Send a packet to the socket. This function cooks output."""
+ text = str(text) # eliminate any unicode or other snigglets
+ text = text.replace(IAC, IAC+IAC)
+ text = text.replace(chr(10), chr(13)+chr(10))
+ self.writecooked(text)
+
+ def writecooked(self, text):
+ """Put data directly into the output queue (bypass output cooker)"""
+ self.sock.sendall(text)
+
+# ------------------------------- Input Cooker -----------------------------
+ def _inputcooker_getc(self, block=True):
+ """Get one character from the raw queue. Optionally blocking.
+ Raise EOFError on end of stream. SHOULD ONLY BE CALLED FROM THE
+ INPUT COOKER."""
+ if self.rawq:
+ ret = self.rawq[0]
+ self.rawq = self.rawq[1:]
+ return ret
+ if not block:
+ if not self.inputcooker_socket_ready():
+ return ''
+ ret = self.sock.recv(20)
+ self.eof = not(ret)
+ self.rawq = self.rawq + ret
+ if self.eof:
+ raise EOFError
+ return self._inputcooker_getc(block)
+
+ #abstractmethod
+ def inputcooker_socket_ready(self):
+ """Indicate that the socket is ready to be read"""
+ # Either use a green select or a real select
+ #return select([self.sock.fileno()], [], [], 0) != ([], [], [])
+ raise NotImplementedError("Please Implement the
inputcooker_socket_ready method")
+
+ def _inputcooker_ungetc(self, char):
+ """Put characters back onto the head of the rawq. SHOULD ONLY
+ BE CALLED FROM THE INPUT COOKER."""
+ self.rawq = char + self.rawq
+
+ def _inputcooker_store(self, char):
+ """Put the cooked data in the correct queue"""
+ if self.sb:
+ self.sbdataq = self.sbdataq + char
+ else:
+ self.inputcooker_store_queue(char)
+
+ #abstractmethod
+ def inputcooker_store_queue(self, char):
+ """Put the cooked data in the output queue (possible locking needed)"""
+ raise NotImplementedError("Please Implement the
inputcooker_store_queue method")
+
+ def inputcooker(self):
+ """Input Cooker - 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.
+ """
+ try:
+ while True:
+ c = self._inputcooker_getc()
+ if not self.iacseq:
+ if c == IAC:
+ self.iacseq += c
+ continue
+ elif c == chr(13) and not(self.sb):
+ c2 = self._inputcooker_getc(block=False)
+ if c2 == theNULL or c2 == '':
+ c = chr(10)
+ elif c2 == chr(10):
+ c = c2
+ else:
+ self._inputcooker_ungetc(c2)
+ c = chr(10)
+ elif c in [x[0] for x in self.ESCSEQ.keys()]:
+ 'Looks like the begining of a key sequence'
+ codes = c
+ for keyseq in self.ESCSEQ.keys():
+ if len(keyseq) == 0:
+ continue
+ while codes == keyseq[:len(codes)] and len(codes)
<= keyseq:
+ if codes == keyseq:
+ c = self.ESCSEQ[keyseq]
+ break
+ codes = codes + self._inputcooker_getc()
+ if codes == keyseq:
+ break
+ self._inputcooker_ungetc(codes[1:])
+ codes = codes[0]
+ self._inputcooker_store(c)
+ elif len(self.iacseq) == 1:
+ 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]'
+ if c in (DO, DONT, WILL, WONT):
+ self.iacseq += c
+ continue
+ self.iacseq = ''
+ if c == IAC:
+ self._inputcooker_store(c)
+ else:
+ if c == SB: # SB ... SE start.
+ self.sb = 1
+ self.sbdataq = ''
+ elif c == SE: # SB ... SE end.
+ self.sb = 0
+ # Callback is supposed to look into
+ # the sbdataq
+ self.options_handler(self.sock, c, NOOPT)
+ elif len(self.iacseq) == 2:
+ cmd = self.iacseq[1]
+ self.iacseq = ''
+ if cmd in (DO, DONT, WILL, WONT):
+ self.options_handler(self.sock, cmd, c)
+ except EOFError:
+ pass
+
+# ------------------------------- Basic Commands ---------------------------
+
+# Format of docstrings for command methods:
+# Line 0: Command paramater(s) if any. (Can be blank line)
+# Line 1: Short descriptive text. (Mandatory)
+# Line 2+: Long descriptive text. (Can be blank line)
+
+ def cmdHELP(self, params):
+ """[<command>]
+ Display help
+ Display either brief help on all commands, or detailed
+ help on a single command passed as a parameter.
+ """
+ if params:
+ cmd = params[0].upper()
+ if self.COMMANDS.has_key(cmd):
+ method = self.COMMANDS[cmd]
+ doc = method.__doc__.split("\n")
+ docp = doc[0].strip()
+ docl = '\n'.join( [l.strip() for l in doc[2:]] )
+ if not docl.strip(): # If there isn't anything here, use line
1
+ docl = doc[1].strip()
+ self.writeline(
+ "%s %s\n\n%s" % (
+ cmd,
+ docp,
+ docl,
+ )
+ )
+ return
+ else:
+ self.writeline("Command '%s' not known" % cmd)
+ else:
+ self.writeline("Help on built in commands\n")
+ keys = self.COMMANDS.keys()
+ keys.sort()
+ for cmd in keys:
+ method = self.COMMANDS[cmd]
+ if getattr(method, 'hidden', False):
+ continue
+ doc = method.__doc__.split("\n")
+ docp = doc[0].strip()
+ docs = doc[1].strip()
+ if len(docp) > 0:
+ docps = "%s - %s" % (docp, docs, )
+ else:
+ docps = "- %s" % (docs, )
+ self.writeline(
+ "%s %s" % (
+ cmd,
+ docps,
+ )
+ )
+ cmdHELP.aliases = ['?']
+
+ def cmdEXIT(self, params):
+ """
+ Exit the command shell
+ """
+ self.RUNSHELL = False
+ self.writeline("Goodbye")
+ cmdEXIT.aliases = ['QUIT', 'BYE', 'LOGOUT']
+
+ def cmdHISTORY(self, params):
+ """
+ Display the command history
+ """
+ cnt = 0
+ self.writeline('Command history\n')
+ for line in self.history:
+ cnt = cnt + 1
+ self.writeline("%-5d : %s" % (cnt, ''.join(line)))
+
+# ----------------------- Command Line Processor Engine --------------------
+
+ def handleException(self, exc_type, exc_param, exc_tb):
+ "Exception handler (False to abort)"
+ self.writeline(''.join( traceback.format_exception(exc_type,
exc_param, exc_tb) ))
+ return True
+
+ def authentication_ok(self):
+ '''Checks the authentication and sets the username of the currently
connected terminal. Returns True or False'''
+ username = None
+ password = None
+ if self.authCallback:
+ if self.authNeedUser:
+ username = self.readline(prompt="Username: ",
use_history=False)
+ if self.authNeedPass:
+ password = self.readline(echo=False, prompt="Password: ",
use_history=False)
+ if self.DOECHO:
+ self.write("\n")
+ try:
+ self.authCallback(username, password)
+ except:
+ self.username = None
+ return False
+ else:
+ # Successful authentication
+ self.username = username
+ return True
+ else:
+ # No authentication desired
+ self.username = None
+ return True
+
+
+ def handle(self):
+ "The actual service to which the user has connected."
+ if not self.authentication_ok():
+ return
+ if self.DOECHO:
+ self.writeline(self.WELCOME)
+ self.session_start()
+ while self.RUNSHELL:
+ raw_input = self.readline(prompt=self.PROMPT).strip()
+ self.input = self.input_reader(self, raw_input)
+ self.raw_input = self.input.raw
+ if self.input.cmd:
+ cmd = self.input.cmd.upper()
+ params = self.input.params
+ if self.COMMANDS.has_key(cmd):
+ try:
+ self.COMMANDS[cmd](params)
+ except:
+ log.exception('Error calling %s.' % cmd)
+ (t, p, tb) = sys.exc_info()
+ if self.handleException(t, p, tb):
+ break
+ else:
+ self.writeerror("Unknown command '%s'" % cmd)
+ log.debug("Exiting handler")
+
+
+
+# vim: set syntax=python ai showmatch:
diff --git a/ryu/contrib/telnetsrv/threaded.py
b/ryu/contrib/telnetsrv/threaded.py
new file mode 100755
index 0000000..3f7a2e7
--- /dev/null
+++ b/ryu/contrib/telnetsrv/threaded.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+# Telnet handler concrete class using true threads.
+
+import threading
+import time
+import select
+
+from telnetsrvlib import TelnetHandlerBase, command
+
+class TelnetHandler(TelnetHandlerBase):
+ "A telnet server handler using Threading"
+ def __init__(self, request, client_address, server):
+ # This is the cooked input stream (list of charcodes)
+ self.cookedq = []
+
+ # Create the locks for handing the input/output queues
+ self.IQUEUELOCK = threading.Lock()
+ self.OQUEUELOCK = threading.Lock()
+
+ # Call the base class init method
+ TelnetHandlerBase.__init__(self, request, client_address, server)
+
+ def setup(self):
+ '''Called after instantiation'''
+ TelnetHandlerBase.setup(self)
+ # Spawn a thread to handle socket input
+ self.thread_ic = threading.Thread(target=self.inputcooker)
+ self.thread_ic.setDaemon(True)
+ self.thread_ic.start()
+ # Note that inputcooker exits on EOF
+
+ # Sleep for 0.5 second to allow options negotiation
+ time.sleep(0.5)
+
+
+ def finish(self):
+ '''Called as the session is ending'''
+ TelnetHandlerBase.finish(self)
+ # Might want to ensure the thread_ic is dead
+
+
+ # -- Threaded input handling functions --
+
+ def getc(self, block=True):
+ """Return one character from the input queue"""
+ if not block:
+ if not len(self.cookedq):
+ return ''
+ while not len(self.cookedq):
+ time.sleep(0.05)
+ self.IQUEUELOCK.acquire()
+ ret = self.cookedq[0]
+ self.cookedq = self.cookedq[1:]
+ self.IQUEUELOCK.release()
+ return ret
+
+ def inputcooker_socket_ready(self):
+ """Indicate that the socket is ready to be read"""
+ return select.select([self.sock.fileno()], [], [], 0) != ([], [], [])
+
+ def inputcooker_store_queue(self, char):
+ """Put the cooked data in the input queue (with locking)"""
+ self.IQUEUELOCK.acquire()
+ if type(char) in [type(()), type([]), type("")]:
+ for v in char:
+ self.cookedq.append(v)
+ else:
+ self.cookedq.append(char)
+ self.IQUEUELOCK.release()
+
+
+ # -- Threaded output handling functions --
+
+ def writemessage(self, text):
+ """Put data in output queue, rebuild the prompt and entered data"""
+ # Need to grab the input queue lock to ensure the entered data doesn't
change
+ # before we're done rebuilding it.
+ # Note that writemessage will eventually call writecooked
+ self.IQUEUELOCK.acquire()
+ TelnetHandlerBase.writemessage(self, text)
+ self.IQUEUELOCK.release()
+
+ def writecooked(self, text):
+ """Put data directly into the output queue"""
+ # Ensure this is the only thread writing
+ self.OQUEUELOCK.acquire()
+ TelnetHandlerBase.writecooked(self, text)
+ self.OQUEUELOCK.release()
+
--
1.7.12
------------------------------------------------------------------------------
Everyone hates slow websites. So do we.
Make your web apps faster with AppDynamics
Download AppDynamics Lite for free today:
http://p.sf.net/sfu/appdyn_d2d_feb
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel