Commit e041080d by btimby

Fixed small bugs in implementation.

 - Separated TCPServer / paramiko.ServerInterface classes.
 - Using BaseRequestHandler instead of StreamRequestHandler (we don't need or want wfile/rfile).
 - Encode paths returned by canonicalize()
 - Use ThreadingMixIn properly (listed first).
parent b3f5828b
...@@ -29,7 +29,7 @@ from __future__ import with_statement ...@@ -29,7 +29,7 @@ from __future__ import with_statement
import os import os
import stat as statinfo import stat as statinfo
import time import time
import SocketServer as sockserv import SocketServer
import threading import threading
import paramiko import paramiko
...@@ -184,7 +184,7 @@ class SFTPServerInterface(paramiko.SFTPServerInterface): ...@@ -184,7 +184,7 @@ class SFTPServerInterface(paramiko.SFTPServerInterface):
return paramiko.SFTP_OK return paramiko.SFTP_OK
def canonicalize(self, path): def canonicalize(self, path):
return abspath(normpath(path)) return abspath(normpath(path)).encode(self.encoding)
@report_sftp_errors @report_sftp_errors
def chattr(self, path, attr): def chattr(self, path, attr):
...@@ -243,33 +243,38 @@ class SFTPHandle(paramiko.SFTPHandle): ...@@ -243,33 +243,38 @@ class SFTPHandle(paramiko.SFTPHandle):
return self.owner.chattr(self.path,attr) return self.owner.chattr(self.path,attr)
class SFTPRequestHandler(sockserv.StreamRequestHandler): class SFTPRequestHandler(SocketServer.BaseRequestHandler):
"""SocketServer RequestHandler subclass for BaseSFTPServer. """SocketServer RequestHandler subclass for BaseSFTPServer.
This RequestHandler subclass creates a paramiko Transport, sets up the This RequestHandler subclass creates a paramiko Transport, sets up the
sftp subsystem, and hands off to the transport's own request handling sftp subsystem, and hands off to the transport's own request handling
thread. Note that paramiko.Transport uses a separate thread by default, thread.
so there is no need to use ThreadingMixin.
""" """
def setup(self):
"""
Creates the SSH transport. Sets security options.
"""
self.transport = paramiko.Transport(self.request)
self.transport.load_server_moduli()
so = self.transport.get_security_options()
so.digests = ('hmac-sha1', )
so.compression = ('zlib@openssh.com', 'none')
self.transport.add_server_key(self.server.host_key)
self.transport.set_subsystem_handler("sftp", paramiko.SFTPServer, SFTPServerInterface, self.server.fs, self.server.encoding)
def handle(self): def handle(self):
t = paramiko.Transport(self.request) """
t.add_server_key(self.server.host_key) Start the paramiko server, this will start a thread to handle the connection.
t.set_subsystem_handler("sftp", paramiko.SFTPServer, SFTPServerInterface, self.server.fs, getattr(self.server,"encoding",None)) """
# Note that this actually spawns a new thread to handle the requests. self.transport.start_server(server=ServerInterface())
# (Actually, paramiko.Transport is a subclass of Thread)
t.start_server(server=self.server)
class ThreadedTCPServer(sockserv.TCPServer, sockserv.ThreadingMixIn): class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass pass
class BaseSFTPServer(ThreadedTCPServer, paramiko.ServerInterface):
"""SocketServer.TCPServer subclass exposing an FS via SFTP.
BaseSFTPServer combines a simple SocketServer.TCPServer subclass with an class BaseSFTPServer(ThreadedTCPServer):
implementation of paramiko.ServerInterface, providing everything that's """SocketServer.TCPServer subclass exposing an FS via SFTP.
needed to expose an FS via SFTP.
Operation is in the standard SocketServer style. The target FS object Operation is in the standard SocketServer style. The target FS object
can be passed into the constructor, or set as an attribute on the server:: can be passed into the constructor, or set as an attribute on the server::
...@@ -280,17 +285,10 @@ class BaseSFTPServer(ThreadedTCPServer, paramiko.ServerInterface): ...@@ -280,17 +285,10 @@ class BaseSFTPServer(ThreadedTCPServer, paramiko.ServerInterface):
It is also possible to specify the host key used by the sever by setting It is also possible to specify the host key used by the sever by setting
the 'host_key' attribute. If this is not specified, it will default to the 'host_key' attribute. If this is not specified, it will default to
the key found in the DEFAULT_HOST_KEY variable. the key found in the DEFAULT_HOST_KEY variable.
Note that this base class allows UNAUTHENTICATED ACCESS to the exposed
FS. This is intentional, since we can't guess what your authentication
needs are. To protect the exposed FS, override the following methods:
* get_allowed_auths Determine the allowed auth modes
* check_auth_none Check auth with no credentials
* check_auth_password Check auth with a password
* check_auth_publickey Check auth with a public key
""" """
# If the server stops/starts quickly, don't fail because of
# "port in use" error.
allow_reuse_address = True
def __init__(self, address, fs=None, encoding=None, host_key=None, RequestHandlerClass=None): def __init__(self, address, fs=None, encoding=None, host_key=None, RequestHandlerClass=None):
self.fs = fs self.fs = fs
...@@ -300,12 +298,30 @@ class BaseSFTPServer(ThreadedTCPServer, paramiko.ServerInterface): ...@@ -300,12 +298,30 @@ class BaseSFTPServer(ThreadedTCPServer, paramiko.ServerInterface):
self.host_key = host_key self.host_key = host_key
if RequestHandlerClass is None: if RequestHandlerClass is None:
RequestHandlerClass = SFTPRequestHandler RequestHandlerClass = SFTPRequestHandler
sockserv.TCPServer.__init__(self,address,RequestHandlerClass) SocketServer.TCPServer.__init__(self,address,RequestHandlerClass)
def shutdown_request(self, request):
# Prevent TCPServer from closing the connection prematurely
return
def close_request(self, request): def close_request(self, request):
# paramiko.Transport closes itself when finished. # Prevent TCPServer from closing the connection prematurely
# If we close it here, we'll break the Transport thread. return
pass
class ServerInterface(paramiko.ServerInterface):
"""
Paramiko ServerInterface implementation that performs user authentication.
Note that this base class allows UNAUTHENTICATED ACCESS to the exposed
FS. This is intentional, since we can't guess what your authentication
needs are. To protect the exposed FS, override the following methods:
* get_allowed_auths Determine the allowed auth modes
* check_auth_none Check auth with no credentials
* check_auth_password Check auth with a password
* check_auth_publickey Check auth with a public key
"""
def check_channel_request(self, kind, chanid): def check_channel_request(self, kind, chanid):
if kind == 'session': if kind == 'session':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment