[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-72]

some framework for adding subsystem handlers in server mode
you can now register a subsystem with a Transport by passing in the name
(like "sftp") and a class (like a hypothetical SFTPServer).  the default
ServerInterface.check_channel_request_subsystem now checks this table in
Transport, and if it finds a match, it creates a new thread for the handler
and calls into it.  a new class SubsystemHandler is added for this purpose
(to be subclassed).
This commit is contained in:
Robey Pointer 2004-09-05 07:41:45 +00:00
parent 6cef2f1259
commit 8dbab50233
2 changed files with 107 additions and 2 deletions

View File

@ -23,6 +23,7 @@ L{ServerInterface} is an interface to override for server support.
"""
from common import *
from transport import BaseTransport
from auth_transport import Transport
class ServerInterface (object):
@ -229,7 +230,14 @@ class ServerInterface (object):
through this channel will be assumed to be connected to the requested
subsystem. An example of a subsystem is C{sftp}.
The default implementation always returns C{False}.
The default implementation checks for a subsystem handler assigned via
L{Transport.set_subsystem_handler <BaseTransport.set_subsystem_handler>}.
If one has been set, the handler is invoked and this method returns
C{True}. Otherwise it returns C{False}.
@note: Because the default implementation uses the L{Transport} to
identify valid subsystems, you probably won't need to override this
method.
@param channel: the L{Channel} the pty request arrived on.
@type channel: L{Channel}
@ -239,7 +247,12 @@ class ServerInterface (object):
subsystem; C{False} if that subsystem can't or won't be provided.
@rtype: boolean
"""
handler_class = channel.get_transport()._get_subsystem_handler(name)
if handler_class is None:
return False
handler = handler_class(channel, name)
handler.start()
return True
def check_channel_window_change_request(self, channel, width, height, pixelwidth, pixelheight):
"""

View File

@ -123,6 +123,72 @@ class SecurityOptions (object):
kex = property(_get_kex, _set_kex, None, "Key exchange algorithms")
class SubsystemHandler (threading.Thread):
"""
Handler for a subsytem in server mode. If you create a subclass of this
class and pass it to
L{Transport.set_subsystem_handler <BaseTransport.set_subsystem_handler>},
an object of this
class will be created for each request for this subsystem. Each new object
will be executed within its own new thread by calling L{start_subsystem}.
When that method completes, the channel is closed.
For example, if you made a subclass C{MP3Handler} and registered it as the
handler for subsystem C{"mp3"}, then whenever a client has successfully
authenticated and requests subsytem C{"mp3"}, an object of class
C{MP3Handler} will be created, and L{start_subsystem} will be called on
it from a new thread.
@since: ivysaur
"""
def __init__(self, channel, name):
threading.Thread.__init__(self, target=self._run)
self.__channel = channel
self.__transport = channel.get_transport()
self.__name = name
def _run(self):
try:
self.__transport._log(DEBUG, 'Starting handler for subsystem %s' % self.__name)
self.start_subsystem(self.__name, self.__transport, self.__channel)
except Exception, e:
self.__transport._log(ERROR, 'Exception in subsystem handler for "%s": %s' %
(self.__name, str(e)))
self.__transport._log(ERROR, util.tb_strings())
try:
self.__channel.close()
except:
pass
def start_subsystem(self, name, transport, channel):
"""
Process an ssh subsystem in server mode. This method is called on a
new object (and in a new thread) for each subsystem request. It is
assumed that all subsystem logic will take place here, and when the
subsystem is finished, this method will return. After this method
returns, the channel is closed.
The combination of C{transport} and C{channel} are unique; this handler
corresponds to exactly one L{Channel} on one L{Transport}.
@note: It is the responsibility of this method to exit if the
underlying L{Transport} is closed. This can be done by checking
L{Transport.is_active <BaseTransport.is_active>} or noticing an EOF
on the L{Channel}.
If this method loops forever without checking for this case, your
python interpreter may refuse to exit because this thread will still
be running.
@param name: name of the requested subsystem.
@type name: str
@param transport: the server-mode L{Transport}.
@type transport: L{Transport}
@param channel: the channel associated with this subsystem request.
@type channel: L{Channel}
"""
pass
class BaseTransport (threading.Thread):
"""
Handles protocol negotiation, key exchange, encryption, and the creation
@ -258,6 +324,7 @@ class BaseTransport (threading.Thread):
self.server_key_dict = { }
self.server_accepts = [ ]
self.server_accept_cv = threading.Condition(self.lock)
self.subsystem_table = { }
def __repr__(self):
"""
@ -779,6 +846,22 @@ class BaseTransport (threading.Thread):
return
def set_subsystem_handler(self, name, handler):
"""
Set the handler class for a subsystem in server mode.
@param name: name of the subsystem.
@type name: str
@param handler: subclass of L{SubsystemHandler} that handles this
subsystem.
@type handler: class
"""
try:
self.lock.acquire()
self.subsystem_table[name] = handler
finally:
self.lock.release()
### internals...
@ -1456,6 +1539,15 @@ class BaseTransport (threading.Thread):
lang = m.get_string()
self._log(DEBUG, 'Debug msg: ' + util.safe_string(msg))
def _get_subsystem_handler(self, name):
try:
self.lock.acquire()
if not self.subsystem_table.has_key(name):
return None
return self.subsystem_table[name]
finally:
self.lock.release()
_handler_table = {
MSG_NEWKEYS: _parse_newkeys,
MSG_GLOBAL_REQUEST: _parse_global_request,