From 8dbab502339064816a391ba45463c87360e88e80 Mon Sep 17 00:00:00 2001 From: Robey Pointer Date: Sun, 5 Sep 2004 07:41:45 +0000 Subject: [PATCH] [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). --- paramiko/server.py | 17 +++++++- paramiko/transport.py | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/paramiko/server.py b/paramiko/server.py index 25924b6..03daba7 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -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 }. + 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 """ - return False + 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): """ diff --git a/paramiko/transport.py b/paramiko/transport.py index 1548671..e017ac0 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -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 }, + 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 } 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,