diff options
author | Robey Pointer <robey@lag.net> | 2004-11-22 07:27:21 +0000 |
---|---|---|
committer | Robey Pointer <robey@lag.net> | 2004-11-22 07:27:21 +0000 |
commit | a8a023a2432753bc6bdfdd0011b66869642845d0 (patch) | |
tree | 7c4b11ad93f31c821d09f29f2c1d510701850583 | |
parent | 611d66428ed7d1885c406e490dfe8bd7a3307fea (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-- | README | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | demo_windows.py | 0 | ||||
-rw-r--r-- | paramiko/__init__.py | 11 | ||||
-rw-r--r-- | paramiko/sftp_handle.py | 174 | ||||
-rw-r--r-- | paramiko/sftp_server.py | 335 | ||||
-rw-r--r-- | paramiko/sftp_si.py | 263 |
6 files changed, 783 insertions, 2 deletions
@@ -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) + |