[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-113]
sftp server support! finally check in sftp_handle (file handle abstraction), sftp_si (server interface), and sftp_server (server implementation) -- all of which make a roughly 90% implementation of server-side sftp.
This commit is contained in:
parent
611d66428e
commit
a8a023a243
2
README
2
README
|
@ -204,4 +204,4 @@ v0.9 FEAROW
|
|||
* multi-part auth not supported (ie, need username AND pk)
|
||||
* server mode needs better documentation
|
||||
* sftp server mode
|
||||
|
||||
* figure out if there's a way to put stdout/stderr on different channels?
|
||||
|
|
|
@ -67,7 +67,7 @@ __license__ = "GNU Lesser General Public License (LGPL)"
|
|||
|
||||
|
||||
import transport, auth_transport, channel, rsakey, dsskey, message, ssh_exception, file
|
||||
import sftp, sftp_client, sftp_attr, sftp_file
|
||||
import sftp, sftp_client, sftp_attr, sftp_file, sftp_handle, sftp_server, sftp_si
|
||||
|
||||
randpool = transport.randpool
|
||||
Transport = auth_transport.Transport
|
||||
|
@ -79,8 +79,11 @@ Message = message.Message
|
|||
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
||||
SFTP = sftp_client.SFTP
|
||||
SFTPClient = sftp_client.SFTPClient
|
||||
SFTPServer = sftp_server.SFTPServer
|
||||
SFTPError = sftp.SFTPError
|
||||
SFTPAttributes = sftp_attr.SFTPAttributes
|
||||
SFTPHandle = sftp_handle.SFTPHandle
|
||||
SFTPServerInterface = sftp_si.SFTPServerInterface
|
||||
ServerInterface = server.ServerInterface
|
||||
SubsystemHandler = server.SubsystemHandler
|
||||
SecurityOptions = transport.SecurityOptions
|
||||
|
@ -103,9 +106,12 @@ __all__ = [ 'Transport',
|
|||
'SSHException',
|
||||
'PasswordRequiredException',
|
||||
'SFTP',
|
||||
'SFTPHandle',
|
||||
'SFTPClient',
|
||||
'SFTPServer',
|
||||
'SFTPError',
|
||||
'SFTPAttributes',
|
||||
'SFTPServerInterface'
|
||||
'ServerInterface',
|
||||
'BufferedFile',
|
||||
'transport',
|
||||
|
@ -117,8 +123,11 @@ __all__ = [ 'Transport',
|
|||
'message',
|
||||
'ssh_exception',
|
||||
'sftp_client',
|
||||
'sftp_server',
|
||||
'sftp_attr',
|
||||
'sftp_file',
|
||||
'sftp_si',
|
||||
'sftp_handle',
|
||||
'server',
|
||||
'file',
|
||||
'util' ]
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Abstraction of an SFTP file handle (for server mode).
|
||||
"""
|
||||
|
||||
import os
|
||||
from common import *
|
||||
from sftp import *
|
||||
|
||||
|
||||
class SFTPHandle (object):
|
||||
"""
|
||||
Abstract object representing a handle to an open file (or folder) on
|
||||
the server. Each handle has a string representation used by the client
|
||||
to refer to the underlying file.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.__name = None
|
||||
# only for handles to folders:
|
||||
self.__files = { }
|
||||
self.__tell = None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
When a client closes a file, this method is called on the handle.
|
||||
Normally you would use this method to close the underlying OS level
|
||||
file object(s).
|
||||
"""
|
||||
pass
|
||||
|
||||
def read(self, offset, length):
|
||||
"""
|
||||
Read up to C{length} bytes from this file, starting at position
|
||||
C{offset}. The offset may be a python long, since SFTP allows it
|
||||
to be 64 bits.
|
||||
|
||||
If the end of the file has been reached, this method may return an
|
||||
empty string to signify EOF, or it may also return L{SFTP_EOF}.
|
||||
|
||||
The default implementation checks for an attribute on C{self} named
|
||||
C{readfile}, and if present, performs the read operation on the python
|
||||
file-like object found there. (This is meant as a time saver for the
|
||||
common case where you are wrapping a python file object.)
|
||||
|
||||
@param offset: position in the file to start reading from.
|
||||
@type offset: int or long
|
||||
@param length: number of bytes to attempt to read.
|
||||
@type length: int
|
||||
@return: data read from the file, or an SFTP error code.
|
||||
@rtype: str
|
||||
"""
|
||||
if not hasattr(self, 'readfile') or (self.readfile is None):
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
try:
|
||||
if self.__tell is None:
|
||||
self.__tell = self.readfile.tell()
|
||||
if offset != self.__tell:
|
||||
self.readfile.seek(offset)
|
||||
self.__tell = offset
|
||||
data = self.readfile.read(length)
|
||||
except IOError, e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
self.__tell += len(data)
|
||||
return data
|
||||
|
||||
def write(self, offset, data):
|
||||
"""
|
||||
Write C{data} into this file at position C{offset}. Extending the
|
||||
file past its original end is expected. Unlike python's normal
|
||||
C{write()} methods, this method cannot do a partial write: it must
|
||||
write all of C{data} or else return an error.
|
||||
|
||||
The default implementation checks for an attribute on C{self} named
|
||||
C{writefile}, and if present, performs the write operation on the
|
||||
python file-like object found there. The attribute is named
|
||||
differently from C{readfile} to make it easy to implement read-only
|
||||
(or write-only) files, but if both attributes are present, they should
|
||||
refer to the same file.
|
||||
|
||||
@param offset: position in the file to start reading from.
|
||||
@type offset: int or long
|
||||
@param data: data to write into the file.
|
||||
@type data: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
"""
|
||||
if not hasattr(self, 'writefile') or (self.writefile is None):
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
try:
|
||||
if self.__tell is None:
|
||||
self.__tell = self.writefile.tell()
|
||||
if offset != self.__tell:
|
||||
self.writefile.seek(offset)
|
||||
self.__tell = offset
|
||||
self.writefile.write(data)
|
||||
except IOError, e:
|
||||
self.__tell = None
|
||||
return SFTPServer.convert_errno(e.errno)
|
||||
self.__tell += len(data)
|
||||
return SFTP_OK
|
||||
|
||||
def stat(self):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object referring to this open file, or an
|
||||
error code. This is equivalent to L{SFTPServerInterface.stat}, except
|
||||
it's called on an open file instead of a path.
|
||||
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, attr):
|
||||
"""
|
||||
Change the attributes of this file. The C{attr} object will contain
|
||||
only those fields provided by the client in its request, so you should
|
||||
check for the presence of fields before using them.
|
||||
|
||||
@param attr: the attributes to change on this file.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _set_files(self, files):
|
||||
"""
|
||||
Used by the SFTP server code to cache a directory listing. (In
|
||||
the SFTP protocol, listing a directory is a multi-stage process
|
||||
requiring a temporary handle.)
|
||||
"""
|
||||
self.__files = files
|
||||
|
||||
def _get_next_files(self):
|
||||
"""
|
||||
Used by the SFTP server code to retreive a cached directory
|
||||
listing.
|
||||
"""
|
||||
fnlist = self.__files[:16]
|
||||
self.__files = self.__files[16:]
|
||||
return fnlist
|
||||
|
||||
def _get_name(self):
|
||||
return self.__name
|
||||
|
||||
def _set_name(self, name):
|
||||
self.__name = name
|
||||
|
||||
|
||||
from sftp_server import SFTPServer
|
|
@ -0,0 +1,335 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
Server-mode SFTP support.
|
||||
"""
|
||||
|
||||
import os, errno
|
||||
from common import *
|
||||
from server import SubsystemHandler
|
||||
from sftp import *
|
||||
from sftp_si import *
|
||||
from sftp_attr import *
|
||||
|
||||
|
||||
class SFTPServer (BaseSFTP, SubsystemHandler):
|
||||
"""
|
||||
Server-side SFTP subsystem support. Since this is a L{SubsystemHandler},
|
||||
it can be (and is meant to be) set as the handler for C{"sftp"} requests.
|
||||
Use L{Transport.set_subsystem_handler} to activate this class.
|
||||
"""
|
||||
|
||||
def __init__(self, channel, name, server=SFTPServerInterface, server_args=None):
|
||||
"""
|
||||
The constructor for SFTPServer is meant to be called from within the
|
||||
L{Transport} as a subsystem handler. The C{server} and C{server_args}
|
||||
parameters are passed from the original call to
|
||||
L{Transport.set_subsystem_handler}.
|
||||
|
||||
@param channel: channel passed from the L{Transport}.
|
||||
@type channel: L{Channel}
|
||||
@param name: name of the requested subsystem.
|
||||
@type name: str
|
||||
@param server: a subclass of L{SFTPServerInterface} to use for handling
|
||||
individual requests.
|
||||
@type server: class
|
||||
@param server_args: keyword parameters to pass to C{server} when it's
|
||||
constructed.
|
||||
@type server_args: dict
|
||||
"""
|
||||
BaseSFTP.__init__(self)
|
||||
SubsystemHandler.__init__(self, channel, name)
|
||||
self.ultra_debug = True
|
||||
self.logger = logging.getLogger('paramiko.chan.' + channel.get_name() + '.sftp')
|
||||
self.next_handle = 1
|
||||
# map of handle-string to SFTPHandle for files & folders:
|
||||
self.file_table = { }
|
||||
self.folder_table = { }
|
||||
if server_args is None:
|
||||
server_args = {}
|
||||
self.server = server(**server_args)
|
||||
|
||||
def start_subsystem(self, name, transport, channel):
|
||||
self.sock = channel
|
||||
self._log(DEBUG, 'Started sftp server on channel %s' % repr(channel))
|
||||
self._send_server_version()
|
||||
self.server.session_started()
|
||||
while True:
|
||||
try:
|
||||
t, data = self._read_packet()
|
||||
except EOFError:
|
||||
self._log(DEBUG, 'EOF -- end of session')
|
||||
return
|
||||
except Exception, e:
|
||||
self._log(DEBUG, 'Exception on channel: ' + str(e))
|
||||
self._log(DEBUG, util.tb_strings())
|
||||
return
|
||||
msg = Message(data)
|
||||
request_number = msg.get_int()
|
||||
self._process(t, request_number, msg)
|
||||
|
||||
def finish_subsystem(self):
|
||||
self.server.session_ended()
|
||||
|
||||
def convert_errno(e):
|
||||
"""
|
||||
Convert an errno value (as from an C{OSError} or C{IOError} into a
|
||||
standard SFTP result code. This is a convenience function for trapping
|
||||
exceptions in server code and returning an appropriate result.
|
||||
|
||||
@param e: an errno code, as from C{OSError.errno}.
|
||||
@type e: int
|
||||
@return: an SFTP error code like L{SFTP_NO_SUCH_fILE}.
|
||||
@rtype: int
|
||||
"""
|
||||
if e == errno.EACCES:
|
||||
# permission denied
|
||||
return SFTP_PERMISSION_DENIED
|
||||
elif e == errno.ENOENT:
|
||||
# no such file
|
||||
return SFTP_NO_SUCH_FILE
|
||||
else:
|
||||
return SFTP_FAILURE
|
||||
convert_errno = staticmethod(convert_errno)
|
||||
|
||||
def set_file_attr(filename, attr):
|
||||
"""
|
||||
Change a file's attributes on the local filesystem. The contents of
|
||||
C{attr} are used to change the permissions, owner, group ownership,
|
||||
and/or modification & access time of the file, depending on which
|
||||
attributes are present in C{attr}.
|
||||
|
||||
This is meant to be a handy helper function for translating SFTP file
|
||||
requests into local file operations.
|
||||
|
||||
@param filename: name of the file to alter (should usually be an
|
||||
absolute path).
|
||||
@type filename: str
|
||||
@param attr: attributes to change.
|
||||
@type attr: L{SFTPAttributes}
|
||||
"""
|
||||
if attr._flags & attr.FLAG_PERMISSIONS:
|
||||
os.chmod(filename, attr.st_mode)
|
||||
if attr._flags & attr.FLAG_UIDGID:
|
||||
os.chown(filename, attr.st_uid, attr.st_gid)
|
||||
if attr._flags & attr.FLAG_AMTIME:
|
||||
os.utime(filename, (attr.st_atime, attr.st_mtime))
|
||||
set_file_attr = staticmethod(set_file_attr)
|
||||
|
||||
|
||||
### internals...
|
||||
|
||||
|
||||
def _response(self, request_number, t, *arg):
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
for item in arg:
|
||||
if type(item) is int:
|
||||
msg.add_int(item)
|
||||
elif type(item) is long:
|
||||
msg.add_int64(item)
|
||||
elif type(item) is str:
|
||||
msg.add_string(item)
|
||||
elif type(item) is SFTPAttributes:
|
||||
item._pack(msg)
|
||||
else:
|
||||
raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item)))
|
||||
self._send_packet(t, str(msg))
|
||||
|
||||
def _send_handle_response(self, request_number, handle, folder=False):
|
||||
if not issubclass(type(handle), SFTPHandle):
|
||||
# must be error code
|
||||
self._send_status(request_number, handle)
|
||||
return
|
||||
handle._set_name('hx%d' % self.next_handle)
|
||||
self.next_handle += 1
|
||||
if folder:
|
||||
self.folder_table[handle._get_name()] = handle
|
||||
else:
|
||||
self.file_table[handle._get_name()] = handle
|
||||
self._response(request_number, CMD_HANDLE, handle._get_name())
|
||||
|
||||
def _send_status(self, request_number, code, desc=None):
|
||||
if desc is None:
|
||||
desc = SFTP_DESC[code]
|
||||
self._response(request_number, CMD_STATUS, code, desc)
|
||||
|
||||
def _open_folder(self, request_number, path):
|
||||
resp = self.server.list_folder(path)
|
||||
if issubclass(type(resp), list):
|
||||
# got an actual list of filenames in the folder
|
||||
folder = SFTPHandle()
|
||||
folder._set_files(resp)
|
||||
self._send_handle_response(request_number, folder, True)
|
||||
return
|
||||
# must be an error code
|
||||
self._send_status(request_number, resp)
|
||||
|
||||
def _read_folder(self, request_number, folder):
|
||||
flist = folder._get_next_files()
|
||||
if len(flist) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
return
|
||||
msg = Message()
|
||||
msg.add_int(request_number)
|
||||
msg.add_int(len(flist))
|
||||
for attr in flist:
|
||||
msg.add_string(attr.filename)
|
||||
msg.add_string(str(attr))
|
||||
attr._pack(msg)
|
||||
self._send_packet(CMD_NAME, str(msg))
|
||||
|
||||
def _convert_pflags(self, pflags):
|
||||
"convert SFTP-style open() flags to python's os.open() flags"
|
||||
if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE):
|
||||
flags = os.O_RDWR
|
||||
elif pflags & SFTP_FLAG_WRITE:
|
||||
flags = os.O_WRONLY
|
||||
else:
|
||||
flags = os.O_RDONLY
|
||||
if pflags & SFTP_FLAG_APPEND:
|
||||
flags |= os.O_APPEND
|
||||
if pflags & SFTP_FLAG_CREATE:
|
||||
flags |= os.O_CREAT
|
||||
if pflags & SFTP_FLAG_TRUNC:
|
||||
flags |= os.O_TRUNC
|
||||
if pflags & SFTP_FLAG_EXCL:
|
||||
flags |= os.O_EXCL
|
||||
return flags
|
||||
|
||||
def _process(self, t, request_number, msg):
|
||||
self._log(DEBUG, 'Request: %s' % CMD_NAMES[t])
|
||||
if t == CMD_OPEN:
|
||||
path = msg.get_string()
|
||||
flags = self._convert_pflags(msg.get_int())
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_handle_response(request_number, self.server.open(path, flags, attr))
|
||||
elif t == CMD_CLOSE:
|
||||
handle = msg.get_string()
|
||||
if self.folder_table.has_key(handle):
|
||||
del self.folder_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
return
|
||||
if self.file_table.has_key(handle):
|
||||
self.file_table[handle].close()
|
||||
del self.file_table[handle]
|
||||
self._send_status(request_number, SFTP_OK)
|
||||
return
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
elif t == CMD_READ:
|
||||
handle = msg.get_string()
|
||||
offset = msg.get_int64()
|
||||
length = msg.get_int()
|
||||
if not self.file_table.has_key(handle):
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
data = self.file_table[handle].read(offset, length)
|
||||
if type(data) is str:
|
||||
if len(data) == 0:
|
||||
self._send_status(request_number, SFTP_EOF)
|
||||
else:
|
||||
self._response(request_number, CMD_DATA, data)
|
||||
else:
|
||||
self._send_status(request_number, data)
|
||||
elif t == CMD_WRITE:
|
||||
handle = msg.get_string()
|
||||
offset = msg.get_int64()
|
||||
data = msg.get_string()
|
||||
if not self.file_table.has_key(handle):
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
self._send_status(request_number, self.file_table[handle].write(offset, data))
|
||||
elif t == CMD_REMOVE:
|
||||
path = msg.get_string()
|
||||
self._send_status(request_number, self.server.remove(path))
|
||||
elif t == CMD_RENAME:
|
||||
oldpath = msg.get_string()
|
||||
newpath = msg.get_string()
|
||||
self._send_status(request_number, self.server.rename(oldpath, newpath))
|
||||
elif t == CMD_MKDIR:
|
||||
path = msg.get_string()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.mkdir(path, attr))
|
||||
elif t == CMD_RMDIR:
|
||||
path = msg.get_string()
|
||||
self._send_status(request_number, self.server.rmdir(path))
|
||||
elif t == CMD_OPENDIR:
|
||||
path = msg.get_string()
|
||||
self._open_folder(request_number, path)
|
||||
return
|
||||
elif t == CMD_READDIR:
|
||||
handle = msg.get_string()
|
||||
if not self.folder_table.has_key(handle):
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
folder = self.folder_table[handle]
|
||||
self._read_folder(request_number, folder)
|
||||
elif t == CMD_STAT:
|
||||
path = msg.get_string()
|
||||
resp = self.server.stat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_LSTAT:
|
||||
path = msg.get_string()
|
||||
resp = self.server.lstat(path)
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_FSTAT:
|
||||
handle = msg.get_string()
|
||||
if not self.file_table.has_key(handle):
|
||||
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
resp = self.file_table[handle].stat()
|
||||
if issubclass(type(resp), SFTPAttributes):
|
||||
self._response(request_number, CMD_ATTRS, resp)
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_SETSTAT:
|
||||
path = msg.get_string()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
self._send_status(request_number, self.server.chattr(path, attr))
|
||||
elif t == CMD_FSETSTAT:
|
||||
handle = msg.get_string()
|
||||
attr = SFTPAttributes._from_msg(msg)
|
||||
if not self.file_table.has_key(handle):
|
||||
self._response(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
|
||||
return
|
||||
self._send_status(request_number, self.file_table[handle].chattr(attr))
|
||||
elif t == CMD_READLINK:
|
||||
path = msg.get_string()
|
||||
resp = self.server.readlink(path)
|
||||
if type(resp) is str:
|
||||
self._response(request_number, CMD_NAME, 1, resp, '', SFTPAttributes())
|
||||
else:
|
||||
self._send_status(request_number, resp)
|
||||
elif t == CMD_REALPATH:
|
||||
path = msg.get_string()
|
||||
rpath = self.server.canonicalize(path)
|
||||
self._response(request_number, CMD_NAME, 1, rpath, '', SFTPAttributes())
|
||||
else:
|
||||
self._send_status(request_number, SFTP_OP_UNSUPPORTED)
|
||||
|
||||
|
||||
from sftp_handle import SFTPHandle
|
|
@ -0,0 +1,263 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
|
||||
"""
|
||||
L{SFTPServerInterface} is an interface to override for SFTP server support.
|
||||
"""
|
||||
|
||||
import os
|
||||
from common import *
|
||||
from sftp import *
|
||||
|
||||
class SFTPServerInterface (object):
|
||||
"""
|
||||
This class defines an interface for controlling the behavior of paramiko
|
||||
when using the L{SFTPServer} subsystem to provide an SFTP server.
|
||||
|
||||
Methods on this class are called from the SFTP session's thread, so you can
|
||||
block as long as necessary without affecting other sessions (even other
|
||||
SFTP sessions). However, raising an exception will usually cause the SFTP
|
||||
session to abruptly end, so you will usually want to catch exceptions and
|
||||
return an appropriate error code.
|
||||
"""
|
||||
|
||||
def session_started(self):
|
||||
"""
|
||||
The SFTP server session has just started. This method is meant to be
|
||||
overridden to perform any necessary setup before handling callbacks
|
||||
from SFTP operations.
|
||||
"""
|
||||
pass
|
||||
|
||||
def session_ended(self):
|
||||
"""
|
||||
The SFTP server session has just ended, either cleanly or via an
|
||||
exception. This method is meant to be overridden to perform any
|
||||
necessary cleanup before this C{SFTPServerInterface} object is
|
||||
destroyed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def open(self, path, flags, attr):
|
||||
"""
|
||||
Open a file on the server and create a handle for future operations
|
||||
on that file. On success, a new object subclassed from L{SFTPHandle}
|
||||
should be returned. This handle will be used for future operations
|
||||
on the file (read, write, etc). On failure, an error code such as
|
||||
L{SFTP_PERMISSION_DENIED} should be returned.
|
||||
|
||||
C{flags} contains the requested mode for opening (read-only,
|
||||
write-append, etc) as a bitset of flags from the C{os} module:
|
||||
- C{os.O_RDONLY}
|
||||
- C{os.O_WRONLY}
|
||||
- C{os.O_RDWR}
|
||||
- C{os.O_APPEND}
|
||||
- C{os.O_CREAT}
|
||||
- C{os.O_TRUNC}
|
||||
- C{os.O_EXCL}
|
||||
(One of C{os.O_RDONLY}, C{os.O_WRONLY}, or C{os.O_RDWR} will always
|
||||
be set.)
|
||||
|
||||
The C{attr} object contains requested attributes of the file if it
|
||||
has to be created. Some or all attribute fields may be missing if
|
||||
the client didn't specify them.
|
||||
|
||||
@note: The SFTP protocol defines all files to be in "binary" mode.
|
||||
There is no equivalent to python's "text" mode.
|
||||
|
||||
@param path: the requested path (relative or absolute) of the file
|
||||
to be opened.
|
||||
@type path: str
|
||||
@param flags: flags or'd together from the C{os} module indicating the
|
||||
requested mode for opening the file.
|
||||
@type flags: int
|
||||
@param attr: requested attributes of the file if it is newly created.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: a new L{SFTPHandle} I{or error code}.
|
||||
@rtype L{SFTPHandle}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def list_folder(self, path):
|
||||
"""
|
||||
Return a list of files within a given folder. The C{path} will use
|
||||
posix notation (C{"/"} separates folder names) and may be an absolute
|
||||
or relative path.
|
||||
|
||||
The list of files is expected to be a list of L{SFTPAttributes}
|
||||
objects, which are similar in structure to the objects returned by
|
||||
C{os.stat}. In addition, each object should have its C{filename}
|
||||
field filled in, since this is important to a directory listing and
|
||||
not normally present in C{os.stat} results. The method
|
||||
L{SFTPAttributes.from_stat} will usually do what you want.
|
||||
|
||||
In case of an error, you should return one of the C{SFTP_*} error
|
||||
codes, such as L{SFTP_PERMISSION_DENIED}.
|
||||
|
||||
@param path: the requested path (relative or absolute) to be listed.
|
||||
@type path: str
|
||||
@return: a list of the files in the given folder, using
|
||||
L{SFTPAttributes} objects.
|
||||
@rtype: list of L{SFTPAttributes} I{or error code}
|
||||
|
||||
@note: You should normalize the given C{path} first (see the
|
||||
C{os.path} module) and check appropriate permissions before returning
|
||||
the list of files. Be careful of malicious clients attempting to use
|
||||
relative paths to escape restricted folders, if you're doing a direct
|
||||
translation from the SFTP server path to your local filesystem.
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def stat(self, path):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object for a path on the server, or an
|
||||
error code. If your server supports symbolic links (also known as
|
||||
"aliases"), you should follow them. (L{lstat} is the corresponding
|
||||
call that doesn't follow symlinks/aliases.)
|
||||
|
||||
@param path: the requested path (relative or absolute) to fetch
|
||||
file statistics for.
|
||||
@type path: str
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def lstat(self, path):
|
||||
"""
|
||||
Return an L{SFTPAttributes} object for a path on the server, or an
|
||||
error code. If your server supports symbolic links (also known as
|
||||
"aliases"), you should I{not} follow them -- instead, you should
|
||||
return data on the symlink or alias itself. (L{stat} is the
|
||||
corresponding call that follows symlinks/aliases.)
|
||||
|
||||
@param path: the requested path (relative or absolute) to fetch
|
||||
file statistics for.
|
||||
@type path: str
|
||||
@return: an attributes object for the given file, or an SFTP error
|
||||
code (like L{SFTP_PERMISSION_DENIED}).
|
||||
@rtype: L{SFTPAttributes} I{or error code}
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def remove(self, path):
|
||||
"""
|
||||
Delete a file, if possible.
|
||||
|
||||
@param path: the requested path (relative or absolute) of the file
|
||||
to delete.
|
||||
@type path: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def rename(self, oldpath, newpath):
|
||||
"""
|
||||
Rename (or move) a file. The SFTP specification implies that this
|
||||
method can be used to move an existing file into a different folder,
|
||||
and since there's no other (easy) way to move files via SFTP, it's
|
||||
probably a good idea to implement "move" in this method too, even for
|
||||
files that cross disk partition boundaries, if at all possible.
|
||||
|
||||
@note: You should return an error if a file with the same name as
|
||||
C{newpath} already exists. (The rename operation should be
|
||||
non-desctructive.)
|
||||
|
||||
@param oldpath: the requested path (relative or absolute) of the
|
||||
existing file.
|
||||
@type oldpath: str
|
||||
@param newpath: the requested new path of the file.
|
||||
@type newpath: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def mkdir(self, path, attr):
|
||||
"""
|
||||
Create a new directory with the given attributes. The C{attr}
|
||||
object may be considered a "hint" and ignored.
|
||||
|
||||
The C{attr} object will contain only those fields provided by the
|
||||
client in its request, so you should use C{hasattr} to check for
|
||||
the presense of fields before using them. In some cases, the C{attr}
|
||||
object may be completely empty.
|
||||
|
||||
@param path: requested path (relative or absolute) of the new
|
||||
folder.
|
||||
@type path: str
|
||||
@param attr: requested attributes of the new folder.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def rmdir(self, path):
|
||||
"""
|
||||
Remove a directory if it exists. The C{path} should refer to an
|
||||
existing, empty folder -- otherwise this method should return an
|
||||
error.
|
||||
|
||||
@param path: requested path (relative or absolute) of the folder
|
||||
to remove.
|
||||
@type path: str
|
||||
@return: an SFTP error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def chattr(self, path, attr):
|
||||
"""
|
||||
Change the attributes of a file. The C{attr} object will contain
|
||||
only those fields provided by the client in its request, so you
|
||||
should check for the presence of fields before using them.
|
||||
|
||||
@param path: requested path (relative or absolute) of the file to
|
||||
change.
|
||||
@type path: str
|
||||
@param attr: requested attributes to change on the file.
|
||||
@type attr: L{SFTPAttributes}
|
||||
@return: an error code like L{SFTP_OK}.
|
||||
@rtype: int
|
||||
"""
|
||||
return SFTP_OP_UNSUPPORTED
|
||||
|
||||
def canonicalize(self, path):
|
||||
"""
|
||||
Return the canonical form of a path on the server. For example,
|
||||
if the server's home folder is C{/home/foo}, the path
|
||||
C{"../betty"} would be canonicalized to C{"/home/betty"}. Note
|
||||
the obvious security issues: if you're serving files only from a
|
||||
specific folder, you probably don't want this method to reveal path
|
||||
names outside that folder.
|
||||
|
||||
You may find the python methods in C{os.path} useful, especially
|
||||
C{os.path.normpath} and C{os.path.realpath}.
|
||||
|
||||
The default implementation returns C{os.path.normpath('/' + path)}.
|
||||
"""
|
||||
if os.path.isabs(path):
|
||||
return os.path.normpath(path)
|
||||
else:
|
||||
return os.path.normpath('/' + path)
|
||||
|
Loading…
Reference in New Issue