Package x2go :: Module sftpserver

[frames] | no frames]

# Source Code for Module x2go.sftpserver

      1  # -*- coding: utf-8 -*-
      2
      3  # Copyright (C) 2010-2013 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
      4
      5  # The Python X2Go sFTPServer code was originally written by Richard Murri,
      6  # for further information see his website: http://www.richardmurri.com
      7
      8  # Python X2Go is free software; you can redistribute it and/or modify
      9  # it under the terms of the GNU Affero General Public License as published by
     10  # the Free Software Foundation; either version 3 of the License, or
     11  # (at your option) any later version.
     12  #
     13  # Python X2Go is distributed in the hope that it will be useful,
     14  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  # GNU Affero General Public License for more details.
     17  #
     18  # You should have received a copy of the GNU Affero General Public License
     19  # along with this program; if not, write to the
     20  # Free Software Foundation, Inc.,
     21  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
     22
     23  """\
     24  For sharing local folders via sFTP/sshfs Python X2Go implements its own sFTP
     25  server (as end point of reverse forwarding tunnel requests). Thus, Python X2Go
     26  does not need a locally installed SSH daemon on the client side machine.
     27
     28  The Python X2Go sFTP server code was originally written by Richard Murri,
     29  for further information see his website: http://www.richardmurri.com
     30
     31  """
     32  __NAME__ = "x2gosftpserver-pylib"
     33
     34  import os
     35  import shutil
     36  import copy
     37  import threading
     38  import paramiko
     39  import gevent
     40
     41  # Python X2Go modules
     42  import rforward
     43  import defaults
     44  import log
     45
     46 -class _SSHServer(paramiko.ServerInterface):
     47      """\
     48      Implementation of a basic SSH server that is supposed
     49      to run with its sFTP server implementation.
     50
     51      """
     52 -    def __init__(self, auth_key=None, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT, *args, **kwargs):
     53          """\
     54          Initialize a new sFTP server interface.
     55
     56          @param auth_key: Server key that the client has to authenticate against
     57          @type auth_key: C{paramiko.RSAKey} instance
     58          @param session_instance: the calling L{X2GoSession} instance
     59          @type session_instance: L{X2GoSession} instance
     60          @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor
     61          @type logger: C{obj}
     62          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
     63              constructed with the given loglevel
     64          @type loglevel: C{int}
     65
     66          """
     67          if logger is None:
     68              self.logger = log.X2GoLogger(loglevel=loglevel)
     69          else:
     70              self.logger = copy.deepcopy(logger)
     71          self.logger.tag = __NAME__
     72
     73          self.current_local_user = defaults.CURRENT_LOCAL_USER
     74          self.auth_key = auth_key
     75          self.session_instance = session_instance
     76          paramiko.ServerInterface.__init__(self, *args, **kwargs)
     77          logger('initializing internal SSH server for handling incoming sFTP requests, allowing connections for user ,,%s\'\' only' % self.current_local_user, loglevel=log.loglevel_DEBUG)
     78
     79 -    def check_channel_request(self, kind, chanid):
     80          """\
     81          Only allow session requests.
     82
     83          @param kind: request type
     84          @type kind: C{str}
     85          @param chanid: channel id (unused)
     86          @type chanid: C{any}
     87
     88          @return: returns a Paramiko/SSH return code
     89          @rtype: C{int}
     90
     91          """
     92          self.logger('detected a channel request for sFTP', loglevel=log.loglevel_DEBUG_SFTPXFER)
     93          if kind == 'session':
     94              return paramiko.OPEN_SUCCEEDED
     95          return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
     96
     97 -    def check_auth_publickey(self, username, key):
     98          """\
     99          Ensure proper authentication.
    100
    101          @param username: username of incoming authentication request
    102          @type username: C{str}
    103          @param key: incoming SSH key to be used for authentication
    104          @type key: C{paramiko.RSAKey} instance
    105
    106          @return: returns a Paramiko/SSH return code
    107          @rtype: C{int}
    108
    109          """
    110          if username == self.current_local_user:
    111              self.logger('sFTP server %s: username is %s' % (self, self.current_local_user), loglevel=log.loglevel_DEBUG)
    112              if type(key) == paramiko.RSAKey and key == self.auth_key:
    113                  self.logger('sFTP server %s: publickey auth has been successful' % (self), loglevel=log.loglevel_INFO)
    114                  return paramiko.AUTH_SUCCESSFUL
    115          self.logger('sFTP server %s: publickey auth failed' % (self), loglevel=log.loglevel_WARN)
    116          return paramiko.AUTH_FAILED
    117
    118 -    def get_allowed_auths(self, username):
    119          """\
    120          Only allow public key authentication.
    121
    122          @param username: username of incoming authentication request
    123          @type username: C{str}
    124
    125          @return: statically returns C{publickey} as auth mechanism
    126          @rtype: C{str}
    127
    128          """
    129          self.logger('sFTP client asked for support auth methods, answering: publickey', loglevel=log.loglevel_DEBUG_SFTPXFER)
    130          return 'publickey'
    131
    132
    133 -class _SFTPHandle(paramiko.SFTPHandle):
    134      """\
    135      Represents a handle to an open file.
    136
    137      """
    138 -    def stat(self):
    139          """\
    140          Create an SFTPAttributes object from an existing stat object (an object returned by os.stat).
    141
    142          return: new C{SFTPAttributes} object with the same attribute fields.
    143          rtype: C{obj}
    144
    145          """
    146          try:
    147              return paramiko.SFTPAttributes.from_stat(os.fstat(self.readfile.fileno()))
    148          except OSError, e:
    149              return paramiko.SFTPServer.convert_errno(e.errno)
    150
    151
    152 -class _SFTPServerInterface(paramiko.SFTPServerInterface):
    153      """\
    154      sFTP server implementation.
    155
    156      """
    157 -    def __init__(self, server, chroot=None, logger=None, loglevel=log.loglevel_DEFAULT, server_event=None, *args, **kwargs):
    158          """\
    159          Make user information accessible as well as set chroot jail directory.
    160
    161          @param server: a C{paramiko.ServerInterface} instance to use with this SFTP server interface
    162          @type server: C{paramiko.ServerInterface} instance
    163          @param chroot: chroot environment for this SFTP interface
    164          @type chroot: C{str}
    165          @param logger: you can pass an L{X2GoLogger} object to the L{X2GoClientXConfig} constructor
    166          @type logger: C{obj}
    167          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
    168              constructed with the given loglevel
    169          @type loglevel: C{int}
    170          @param server_event: a C{threading.Event} instance that can signal SFTP session termination
    171          @type server_event: C{threading.Event} instance
    172
    173          """
    174          if logger is None:
    175              self.logger = log.X2GoLogger(loglevel=loglevel)
    176          else:
    177              self.logger = copy.deepcopy(logger)
    178          self.logger.tag = __NAME__
    179          self.server_event = server_event
    180
    181          self.logger('sFTP server: initializing new channel...', loglevel=log.loglevel_DEBUG)
    182          self.CHROOT = chroot or '/tmp'
    183
    184 -    def _realpath(self, path):
    185          """\
    186          Enforce the chroot jail. On Windows systems the drive letter is incorporated in the
    187          chroot path name (/windrive/<drive_letter>/path/to/file/or/folder).
    188
    189          @param path: path name within chroot
    190          @type path: C{str}
    191
    192          @return: real path name (including drive letter on Windows systems)
    193          @rtype: C{str}
    194
    195          """
    196          if defaults.X2GOCLIENT_OS == 'Windows' and path.startswith('/windrive'):
    197              _path_components = path.split('/')
    198              _drive = _path_components[2]
    199              _tail_components = (len(_path_components) > 3) and _path_components[3:] or ''
    200              _tail = os.path.normpath('/'.join(_tail_components))
    201              path = os.path.join('%s:' % _drive, '/', _tail)
    202          else:
    203              path = self.CHROOT + self.canonicalize(path)
    204              path = path.replace('//', '/')
    205          return path
    206
    207 -    def list_folder(self, path):
    208          """\
    209          List the contents of a folder.
    210
    211          @param path: path to folder
    212          @type path: C{str}
    213
    214          @return: returns the folder contents, on failure returns a Paramiko/SSH return code
    215          @rtype: C{dict} or C{int}
    216
    217          """
    218          path = self._realpath(path)
    219          self.logger('sFTP server: listing files in folder: %s' % path, loglevel=log.loglevel_DEBUG_SFTPXFER)
    220
    221          try:
    222              out = []
    223              flist = os.listdir(path)
    224              for fname in flist:
    225
    226                  try:
    227                      attr = paramiko.SFTPAttributes.from_stat(os.lstat(os.path.join(path, fname)))
    228                      attr.filename = fname
    229                      self.logger('sFTP server %s: file attributes ok: %s' % (self, fname), loglevel=log.loglevel_DEBUG_SFTPXFER)
    230                      out.append(attr)
    231                  except OSError, e:
    232                      self.logger('sFTP server %s: encountered error processing attributes of file %s: %s' % (self, fname, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    233
    234              self.logger('sFTP server: folder list is : %s' % str([ a.filename for a in out ]), loglevel=log.loglevel_DEBUG_SFTPXFER)
    235              return out
    236          except OSError, e:
    237              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    238              return paramiko.SFTPServer.convert_errno(e.errno)
    239
    240 -    def stat(self, path):
    241          """\
    242          Stat on a file.
    243
    244          @param path: path to file/folder
    245          @type path: C{str}
    246
    247          @return: returns the file's stat output, on failure: returns a Paramiko/SSH return code
    248          @rtype: C{class} or C{int}
    249
    250          """
    251          path = self._realpath(path)
    252          self.logger('sFTP server %s: calling stat on path: %s' % (self, path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    253          try:
    254              return paramiko.SFTPAttributes.from_stat(os.stat(path))
    255          except OSError, e:
    256              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    257              return paramiko.SFTPServer.convert_errno(e.errno)
    258
    259 -    def lstat(self, path):
    260          """\
    261          LStat on a file.
    262
    263          @param path: path to folder
    264          @type path: C{str}
    265
    266          @return: returns the file's lstat output, on failure: returns a Paramiko/SSH return code
    267          @rtype: C{class} or C{int}
    268
    269          """
    270          path = self._realpath(path)
    271          self.logger('sFTP server: calling lstat on path: %s' % path, loglevel=log.loglevel_DEBUG_SFTPXFER)
    272          try:
    273              return paramiko.SFTPAttributes.from_stat(os.lstat(path))
    274          except OSError, e:
    275              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    276              return paramiko.SFTPServer.convert_errno(e.errno)
    277
    278 -    def open(self, path, flags, attr):
    279          """\
    280          Open a file for reading, writing, appending etc.
    281
    282          @param path: path to file
    283          @type path: C{str}
    284          @param flags: file flags
    285          @type flags: C{str}
    286          @param attr: file attributes
    287          @type attr: C{class}
    288
    289          @return: file handle/object for remote file, on failure: returns a Paramiko/SSH return code
    290          @rtype: L{_SFTPHandle} instance or C{int}
    291
    292          """
    293          path = self._realpath(path)
    294          self.logger('sFTP server %s: opening file: %s' % (self, path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    295          try:
    296              binary_flag = getattr(os, 'O_BINARY',  0)
    297              flags |= binary_flag
    298              mode = getattr(attr, 'st_mode', None)
    299              if mode is not None:
    300                  fd = os.open(path, flags, mode)
    301              else:
    302                  # os.open() defaults to 0777 which is
    303                  # an odd default mode for files
    304                  fd = os.open(path, flags, 0666)
    305          except OSError, e:
    306              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    307              return paramiko.SFTPServer.convert_errno(e.errno)
    308          if (flags & os.O_CREAT) and (attr is not None):
    309              attr._flags &= ~attr.FLAG_PERMISSIONS
    310              paramiko.SFTPServer.set_file_attr(path, attr)
    311          if flags & os.O_WRONLY:
    312              if flags & os.O_APPEND:
    313                  fstr = 'ab'
    314              else:
    315                  fstr = 'wb'
    316          elif flags & os.O_RDWR:
    317              if flags & os.O_APPEND:
    318                  fstr = 'a+b'
    319              else:
    320                  fstr = 'r+b'
    321          else:
    322              # O_RDONLY (== 0)
    323              fstr = 'rb'
    324          try:
    325              f = os.fdopen(fd, fstr)
    326          except OSError, e:
    327              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    328              return paramiko.SFTPServer.convert_errno(e.errno)
    329          fobj = _SFTPHandle(flags)
    330          fobj.filename = path
    331          fobj.readfile = f
    332          fobj.writefile = f
    333          return fobj
    334
    335 -    def remove(self, path):
    336          """\
    337          Remove a file.
    338
    339          @param path: path to file
    340          @type path: C{str}
    341
    342          @return: returns Paramiko/SSH return code
    343          @rtype: C{int}
    344
    345          """
    346          path = self._realpath(path)
    347          os.remove(path)
    348          self.logger('sFTP server %s: removing file: %s' % (self, path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    349          return paramiko.SFTP_OK
    350
    351 -    def rename(self, oldpath, newpath):
    352          """\
    353          Rename/move a file.
    354
    355          @param oldpath: old path/location/file name
    356          @type oldpath: C{str}
    357          @param newpath: new path/location/file name
    358          @type newpath: C{str}
    359
    360          @return: returns Paramiko/SSH return code
    361          @rtype: C{int}
    362
    363          """
    364          self.logger('sFTP server %s: renaming path from %s to %s' % (self, oldpath, newpath), loglevel=log.loglevel_DEBUG_SFTPXFER)
    365          oldpath = self._realpath(oldpath)
    366          newpath = self._realpath(newpath)
    367          try:
    368              shutil.move(oldpath, newpath)
    369          except OSError, e:
    370              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    371              return paramiko.SFTPServer.convert_errno(e.errno)
    372          return paramiko.SFTP_OK
    373
    374 -    def mkdir(self, path, attr):
    375          """\
    376          Make a directory.
    377
    378          @param path: path to new folder
    379          @type path: C{str}
    380          @param attr: file attributes
    381          @type attr: C{class}
    382
    383          @return: returns Paramiko/SSH return code
    384          @rtype: C{int}
    385
    386          """
    387          self.logger('sFTP server: creating new dir (perms: %s): %s' % (attr.st_mode, path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    388          path = self._realpath(path)
    389          try:
    390              os.mkdir(path, attr.st_mode)
    391          except OSError, e:
    392              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    393              return paramiko.SFTPServer.convert_errno(e.errno)
    394          return paramiko.SFTP_OK
    395
    396 -    def rmdir(self, path):
    397          """\
    398          Remove a directory (if needed recursively).
    399
    400          @param path: folder to be removed
    401          @type path: C{str}
    402
    403          @return: returns Paramiko/SSH return code
    404          @rtype: C{int}
    405
    406          """
    407          self.logger('sFTP server %s: removing dir: %s' % (self, path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    408          path = self._realpath(path)
    409          try:
    410              shutil.rmtree(path)
    411          except OSError, e:
    412              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    413              return paramiko.SFTPServer.convert_errno(e.errno)
    414          return paramiko.SFTP_OK
    415
    416 -    def chattr(self, path, attr):
    417          """\
    418          Change file attributes.
    419
    420          @param path: path of file/folder
    421          @type path: C{str}
    422          @param attr: new file attributes
    423          @type attr: C{class}
    424
    425          @return: returns Paramiko/SSH return code
    426          @rtype: C{int}
    427
    428          """
    429          self.logger('sFTP server %s: modifying attributes of path: %s' % (self, path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    430          path = self._realpath(path)
    431          try:
    432              if attr.st_mode is not None:
    433                  os.chmod(path, attr.st_mode)
    434              if attr.st_uid is not None:
    435                  os.chown(path, attr.st_uid, attr.st_gid)
    436          except OSError, e:
    437              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    438              return paramiko.SFTPServer.convert_errno(e.errno)
    439          return paramiko.SFTP_OK
    440
    441 -    def symlink(self, target_path, path):
    442          """\
    443          Create a symbolic link.
    444
    445          @param target_path: link shall point to this path
    446          @type target_path: C{str}
    447          @param path: link location
    448          @type path: C{str}
    449
    450          @return: returns Paramiko/SSH return code
    451          @rtype: C{int}
    452
    453          """
    454          self.logger('sFTP server %s: creating symlink from: %s to target: %s' % (self, path, target_path), loglevel=log.loglevel_DEBUG_SFTPXFER)
    455          path = self._realpath(path)
    456          if target_path.startswith('/'):
    457              target_path = self._realpath(target_path)
    458          try:
    459              os.symlink(target_path, path)
    460          except OSError, e:
    461              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    462              return paramiko.SFTPServer.convert_errno(e.errno)
    463          return paramiko.SFTP_OK
    464
    465 -    def readlink(self, path):
    466          """\
    467          Read the target of a symbolic link.
    468
    469          @param path: path of symbolic link
    470          @type path: C{str}
    471
    472          @return: target location of the symbolic link, on failure: returns a Paramiko/SSH return code
    473          @rtype: C{str} or C{int}
    474
    475          """
    476          path = self._realpath(path)
    477          try:
    478              return os.readlink(path)
    479          except OSError, e:
    480              self.logger('sFTP server %s: encountered error: %s' % (self, str(e)), loglevel=log.loglevel_DEBUG_SFTPXFER)
    481              return paramiko.SFTPServer.convert_errno(e.errno)
    482
    483 -    def session_ended(self):
    484          """\
    485          Tidy up when the sFTP session has ended.
    486
    487          """
    488          if self.server_event is not None:
    489              self.logger('sFTP server %s: session has ended' % self, loglevel=log.loglevel_DEBUG_SFTPXFER)
    490              self.server_event.set()
    491
    492
    493 -class X2GoRevFwTunnelToSFTP(rforward.X2GoRevFwTunnel):
    494      """\
    495      A reverse fowarding tunnel with an sFTP server at its endpoint. This blend of a Paramiko/SSH
    496      reverse forwarding tunnel is used to provide access to local X2Go client folders
    497      from within the the remote X2Go server session.
    498
    499      """
    500 -    def __init__(self, server_port, ssh_transport, auth_key=None, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
    501          """\
    502          Start a Paramiko/SSH reverse forwarding tunnel, that has an sFTP server listening at
    503          the endpoint of the tunnel.
    504
    505          @param server_port: the TCP/IP port on the X2Go server (starting point of the tunnel),
    506              normally some number above 30000
    507          @type server_port: C{int}
    508          @param ssh_transport: the L{X2GoSession}'s Paramiko/SSH transport instance
    509          @type ssh_transport: C{paramiko.Transport} instance
    510          @param auth_key: Paramiko/SSH RSAkey object that has to be authenticated against by
    511              the remote sFTP client
    512          @type auth_key: C{paramiko.RSAKey} instance
    513          @param logger: you can pass an L{X2GoLogger} object to the
    514              L{X2GoRevFwTunnelToSFTP} constructor
    515          @type logger: L{X2GoLogger} instance
    516          @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
    517              constructed with the given loglevel
    518          @type loglevel: C{int}
    519
    520          """
    521          self.ready = False
    522          if logger is None:
    523              self.logger = log.X2GoLogger(loglevel=loglevel)
    524          else:
    525              self.logger = copy.deepcopy(logger)
    526          self.logger.tag = __NAME__
    527
    528          self.server_port = server_port
    529          self.ssh_transport = ssh_transport
    530          self.session_instance = session_instance
    531          if type(auth_key) is not paramiko.RSAKey:
    532              auth_key = None
    533          self.auth_key = auth_key
    534
    535          self.open_channels = {}
    536          self.incoming_channel = threading.Condition()
    537
    538          threading.Thread.__init__(self)
    539          self.daemon = True
    540          self._accept_channels = True
    541
    542 -    def run(self):
    543          """\
    544          This method gets run once an L{X2GoRevFwTunnelToSFTP} has been started with its
    545          L{start()} method. Use L{X2GoRevFwTunnelToSFTP}.stop_thread() to stop the
    546          reverse forwarding tunnel again (refer also to its pause() and resume() method).
    547
    548          L{X2GoRevFwTunnelToSFTP.run()} waits for notifications of an appropriate incoming
    549          Paramiko/SSH channel (issued by L{X2GoRevFwTunnelToSFTP.notify()}). Appropriate in
    550          this context means, that its starting point on the X2Go server matches the class's
    551          property C{server_port}.
    552
    553          Once a new incoming channel gets announced by the L{notify()} method, a new
    554          L{X2GoRevFwSFTPChannelThread} instance will be initialized. As a data stream handler,
    555          the function L{x2go_rev_forward_sftpchannel_handler()} will be used.
    556
    557          The channel will last till the connection gets dropped on the X2Go server side or
    558          until the tunnel gets paused by an L{X2GoRevFwTunnelToSFTP.pause()} call or
    559          stopped via the C{X2GoRevFwTunnelToSFTP.stop_thread()} method.
    560
    561          """
    562          self._request_port_forwarding()
    563          self._keepalive = True
    564          self.ready = True
    565          while self._keepalive:
    566
    567              self.incoming_channel.acquire()
    568
    569              self.logger('waiting for incoming sFTP channel on X2Go server port: [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG)
    570              self.incoming_channel.wait()
    571              if self._keepalive:
    572                  self.logger('Detected incoming sFTP channel on X2Go server port: [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG)
    573                  _chan = self.ssh_transport.accept()
    574                  self.logger('sFTP channel %s for server port [localhost]:%s is up' % (_chan, self.server_port), loglevel=log.loglevel_DEBUG)
    575              else:
    576                  self.logger('closing down rev forwarding sFTP tunnel on remote end [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG)
    577
    578              self.incoming_channel.release()
    579              if self._accept_channels and self._keepalive:
    580                  _new_chan_thread = X2GoRevFwSFTPChannelThread(_chan,
    581                                                                target=x2go_rev_forward_sftpchannel_handler,
    582                                                                kwargs={
    583                                                                 'chan': _chan,
    584                                                                 'auth_key': self.auth_key,
    585                                                                 'logger': self.logger,
    586                                                                }
    587                                                               )
    588                  _new_chan_thread.start()
    589                  self.open_channels['[%s]:%s' % _chan.origin_addr] = _new_chan_thread
    590          self.ready = False
    591
    592
    593 -def x2go_rev_forward_sftpchannel_handler(chan=None, auth_key=None, logger=None):
    594      """\
    595      Handle incoming sFTP channels that got setup by an L{X2GoRevFwTunnelToSFTP} instance.
    596
    597      The channel (and the corresponding connections) close either ...
    598
    599          - ... if the connecting application closes the connection and thus, drops
    600          the sFTP channel, or
    601          - ... if the L{X2GoRevFwTunnelToSFTP} parent thread gets paused. The call
    602          of L{X2GoRevFwTunnelToSFTP.pause()} on the instance can be used to shut down all incoming
    603          tunneled SSH connections associated to this L{X2GoRevFwTunnelToSFTP} instance
    604          from within a Python X2Go application.
    605
    606      @param chan: an incoming sFTP channel
    607      @type chan: paramiko.Channel instance
    608      @param auth_key: Paramiko/SSH RSAkey object that has to be authenticated against by
    609          the remote sFTP client
    610      @type auth_key: C{paramiko.RSAKey} instance
    611      @param logger: you must pass an L{X2GoLogger} object to this handler method
    612      @type logger: C{X2GoLogger} instance
    613
    614      """
    615      if logger is None:
    616          def _dummy_logger(msg, l):
    617              pass
    618          logger = _dummy_logger
    619
    620      if auth_key is None:
    621          logger('sFTP channel %s closed because of missing authentication key' % chan, loglevel=log.loglevel_DEBUG)
    622          return
    623
    624      # set up server
    625      t = paramiko.Transport(chan)
    626      t.daemon = True
    627      t.load_server_moduli()
    628      t.add_server_key(defaults.RSAHostKey)
    629
    630      # set up sftp handler, server and event
    631      event = threading.Event()
    632      t.set_subsystem_handler('sftp', paramiko.SFTPServer, sftp_si=_SFTPServerInterface, chroot='/', logger=logger, server_event=event)
    633      logger('registered sFTP subsystem handler', loglevel=log.loglevel_DEBUG_SFTPXFER)
    634      server = _SSHServer(auth_key=auth_key, logger=logger)
    635
    636      # start ssh server session
    637      t.start_server(server=server, event=event)
    638
    639      while t.is_active():
    640          gevent.sleep(1)
    641
    642      t.stop_thread()
    643      logger('sFTP channel %s closed down' % chan, loglevel=log.loglevel_DEBUG)
    644
    645
    646 -class X2GoRevFwSFTPChannelThread(rforward.X2GoRevFwChannelThread): pass
    647  """A clone of L{rforward.X2GoRevFwChannelThread}."""
    648