summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-11-22 07:27:21 +0000
committerRobey Pointer <robey@lag.net>2004-11-22 07:27:21 +0000
commita8a023a2432753bc6bdfdd0011b66869642845d0 (patch)
tree7c4b11ad93f31c821d09f29f2c1d510701850583
parent611d66428ed7d1885c406e490dfe8bd7a3307fea (diff)
[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.
-rw-r--r--README2
-rwxr-xr-x[-rw-r--r--]demo_windows.py0
-rw-r--r--paramiko/__init__.py11
-rw-r--r--paramiko/sftp_handle.py174
-rw-r--r--paramiko/sftp_server.py335
-rw-r--r--paramiko/sftp_si.py263
6 files changed, 783 insertions, 2 deletions
diff --git a/README b/README
index 1360c17d..32184944 100644
--- a/README
+++ b/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?
diff --git a/demo_windows.py b/demo_windows.py
index 5d870907..5d870907 100644..100755
--- a/demo_windows.py
+++ b/demo_windows.py
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index a8b38bcd..f7ddbc1c 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -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' ]
diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py
new file mode 100644
index 00000000..5f202969
--- /dev/null
+++ b/paramiko/sftp_handle.py
@@ -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
diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py
new file mode 100644
index 00000000..22c1f39e
--- /dev/null
+++ b/paramiko/sftp_server.py
@@ -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
diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py
new file mode 100644
index 00000000..3f77a3fb
--- /dev/null
+++ b/paramiko/sftp_si.py
@@ -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)
+