merged with trunk
[odoo/odoo.git] / addons / document_ftp / ftpserver / ftpserver.py
old mode 100644 (file)
new mode 100755 (executable)
index 8e10f55..bd72742
@@ -121,28 +121,22 @@ import tempfile
 import warnings
 import random
 import stat
+from collections import deque
 from tarfile import filemode
 
-try:
-    import pwd
-    import grp
-except ImportError:
-    pwd = grp = None
-
-
 LOG_ACTIVE = True
 
 __all__ = ['proto_cmds', 'Error', 'log', 'logline', 'logerror', 'DummyAuthorizer',
            'FTPHandler', 'FTPServer', 'PassiveDTP', 'ActiveDTP', 'DTPHandler',
            'FileProducer', 'IteratorProducer', 'BufferedIteratorProducer',
-           'AbstractedFS', ]
+           'AbstractedFS',]
 
 
-__pname__ = 'Python FTP server library (pyftpdlib)'
-__ver__ = '0.4.0'
-__date__ = '2008-05-16'
-__author__ = "Giampaolo Rodola' <g.rodola@gmail.com>"
-__web__ = 'http://code.google.com/p/pyftpdlib/'
+__pname__   = 'Python FTP server library (pyftpdlib)'
+__ver__     = '0.4.0'
+__date__    = '2008-05-16'
+__author__  = "Giampaolo Rodola' <g.rodola@gmail.com>"
+__web__     = 'http://code.google.com/p/pyftpdlib/'
 
 
 proto_cmds = {
@@ -192,24 +186,6 @@ proto_cmds = {
     }
 
 
-# hack around format_exc function of traceback module to grant
-# backward compatibility with python < 2.4
-if not hasattr(traceback, 'format_exc'):
-    try:
-        import cStringIO as StringIO
-    except ImportError:
-        import StringIO
-
-    def _format_exc():
-        f = StringIO.StringIO()
-        traceback.print_exc(file=f)
-        data = f.getvalue()
-        f.close()
-        return data
-
-    traceback.format_exc = _format_exc
-
-
 def _strerror(err):
     """A wrap around os.strerror() which may be not available on all
     platforms (e.g. pythonCE).
@@ -221,6 +197,33 @@ def _strerror(err):
     else:
         return err.strerror
 
+def _to_unicode(s):
+    try:
+        return s.decode('utf-8')
+    except UnicodeError:
+        pass
+    try:
+        return s.decode('latin')
+    except UnicodeError:
+        pass
+    try:
+        return s.encode('ascii')
+    except UnicodeError:
+        return s
+
+def _to_decode(s):
+    try:
+        return s.encode('utf-8')
+    except UnicodeError:
+        pass
+    try:
+        return s.encode('latin')
+    except UnicodeError:
+        pass
+    try:
+        return s.decode('ascii')
+    except UnicodeError:
+        return s
 
 # --- library defined exceptions
 
@@ -299,13 +302,13 @@ class DummyAuthorizer:
         provide customized response strings when user log-in and quit.
         """
         if self.has_user(username):
-            raise AuthorizerError('User "%s" already exists' % username)
+            raise AuthorizerError('User "%s" already exists' %username)
         homedir = os.path.realpath(homedir)
         if not os.path.isdir(homedir):
-            raise AuthorizerError('No such directory: "%s"' % homedir)
+            raise AuthorizerError('No such directory: "%s"' %homedir)
         for p in perm:
             if p not in 'elradfmw':
-                raise AuthorizerError('No such permission "%s"' % p)
+                raise AuthorizerError('No such permission "%s"' %p)
         for p in perm:
             if (p in self.write_perms) and (username == 'anonymous'):
                 warnings.warn("write permissions assigned to anonymous user.",
@@ -421,7 +424,7 @@ class PassiveDTP(asyncore.dispatcher):
         else:
             ports = list(self.cmd_channel.passive_ports)
             while ports:
-                port = ports.pop(random.randint(0, len(ports) - 1))
+                port = ports.pop(random.randint(0, len(ports) -1))
                 try:
                     self.bind((ip, port))
                 except socket.error, why:
@@ -451,11 +454,11 @@ class PassiveDTP(asyncore.dispatcher):
                 ip = self.cmd_channel.masquerade_address
             # The format of 227 response in not standardized.
             # This is the most expected:
-            self.cmd_channel.respond('227 Entering passive mode (%s,%d,%d).' % (
+            self.cmd_channel.respond('227 Entering passive mode (%s,%d,%d).' %(
                     ip.replace('.', ','), port / 256, port % 256))
         else:
             self.cmd_channel.respond('229 Entering extended passive mode '
-                                     '(|||%d|).' % port)
+                                     '(|||%d|).' %port)
 
     # --- connection / overridden
 
@@ -473,15 +476,15 @@ class PassiveDTP(asyncore.dispatcher):
                 except socket.error:
                     pass
                 msg = 'Rejected data connection from foreign address %s:%s.' \
-                        % (addr[0], addr[1])
-                self.cmd_channel.respond("425 %s" % msg)
+                        %(addr[0], addr[1])
+                self.cmd_channel.respond("425 %s" %msg)
                 self.cmd_channel.log(msg)
                 # do not close listening socket: it couldn't be client's blame
                 return
             else:
                 # site-to-site FTP allowed
                 msg = 'Established data connection with foreign address %s:%s.'\
-                        % (addr[0], addr[1])
+                        %(addr[0], addr[1])
                 self.cmd_channel.log(msg)
         # Immediately close the current channel (we accept only one
         # connection at time) and avoid running out of max connections
@@ -544,7 +547,6 @@ class ActiveDTP(asyncore.dispatcher):
         handler = self.cmd_channel.dtp_handler(self.socket, self.cmd_channel)
         self.cmd_channel.data_channel = handler
         self.cmd_channel.on_dtp_connection()
-        #self.close()  # <-- (done automatically)
 
     def handle_expt(self):
         self.cmd_channel.respond("425 Can't connect to specified address.")
@@ -563,16 +565,6 @@ class ActiveDTP(asyncore.dispatcher):
         self.cmd_channel.respond("425 Can't connect to specified address.")
         self.close()
 
-
-try:
-    from collections import deque
-except ImportError:
-    # backward compatibility with Python < 2.4 by replacing deque with a list
-    class deque(list):
-        def appendleft(self, obj):
-            list.insert(self, 0, obj)
-
-
 class DTPHandler(asyncore.dispatcher):
     """Class handling server-data-transfer-process (server-DTP, see
     RFC-959) managing data-transfer operations involving sending
@@ -609,7 +601,7 @@ class DTPHandler(asyncore.dispatcher):
     """
 
     ac_in_buffer_size = 8192
-    ac_out_buffer_size = 8192
+    ac_out_buffer_size  = 8192
 
     def __init__(self, sock_obj, cmd_channel):
         """Initialize the command channel.
@@ -686,7 +678,7 @@ class DTPHandler(asyncore.dispatcher):
         sabs = self.ac_out_buffer_size
         if len(data) > sabs:
             for i in xrange(0, len(data), sabs):
-                self.producer_fifo.append(data[i:i + sabs])
+                self.producer_fifo.append(data[i:i+sabs])
         else:
             self.producer_fifo.append(data)
         self.initiate_send()
@@ -776,7 +768,7 @@ class DTPHandler(asyncore.dispatcher):
             # confidential error messages
             logerror(traceback.format_exc())
             error = "Internal error"
-        self.cmd_channel.respond("426 %s; transfer aborted." % error)
+        self.cmd_channel.respond("426 %s; transfer aborted." %error)
         self.close()
 
     def handle_close(self):
@@ -793,11 +785,11 @@ class DTPHandler(asyncore.dispatcher):
         if self.transfer_finished:
             self.cmd_channel.respond("226 Transfer complete.")
             if self.file_obj:
-                fname = self.cmd_channel.fs.fs2ftp(self.file_obj.name)
-                self.cmd_channel.log('"%s" %s.' % (fname, action))
+                fname = self.file_obj.name
+                self.cmd_channel.log('"%s" %s.' %(fname, action))
         else:
             tot_bytes = self.get_transmitted_bytes()
-            msg = "Transfer aborted; %d bytes transmitted." % tot_bytes
+            msg = "Transfer aborted; %d bytes transmitted." %tot_bytes
             self.cmd_channel.respond("426 " + msg)
             self.cmd_channel.log(msg)
         self.close()
@@ -1138,19 +1130,11 @@ class AbstractedFS:
 
     # note: the following operations are no more blocking
 
-    def get_list_dir(self, path):
+    def get_list_dir(self, datacr):
         """"Return an iterator object that yields a directory listing
         in a form suitable for LIST command.
         """
-        if self.isdir(path):
-            listing = self.listdir(path)
-            listing.sort()
-            return self.format_list(path, listing)
-        # if path is a file or a symlink we return information about it
-        else:
-            basedir, filename = os.path.split(path)
-            self.lstat(path)  # raise exc in case of problems
-            return self.format_list(basedir, [filename])
+        raise DeprecationWarning()
 
     def get_stat_dir(self, rawline):
         """Return an iterator object that yields a list of files
@@ -1208,21 +1192,9 @@ class AbstractedFS:
             if not nlinks:  # non-posix system, let's use a bogus value
                 nlinks = 1
             size = st.st_size  # file size
-            if pwd and grp:
-                # get user and group name, else just use the raw uid/gid
-                try:
-                    uname = pwd.getpwuid(st.st_uid).pw_name
-                except KeyError:
-                    uname = st.st_uid
-                try:
-                    gname = grp.getgrgid(st.st_gid).gr_name
-                except KeyError:
-                    gname = st.st_gid
-            else:
-                # on non-posix systems the only chance we use default
-                # bogus values for owner and group
-                uname = "owner"
-                gname = "group"
+            uname = st.st_uid or "owner"
+            gname = st.st_gid or "group"
+
             # stat.st_mtime could fail (-1) if last mtime is too old
             # in which case we return the local time as last mtime
             try:
@@ -1234,7 +1206,7 @@ class AbstractedFS:
                 basename = basename + " -> " + os.readlink(file)
 
             # formatting is matched with proftpd ls output
-            yield "%s %3s %-8s %-8s %8s %s %s\r\n" % (perms, nlinks, uname, gname,
+            yield "%s %3s %-8s %-8s %8s %s %s\r\n" %(perms, nlinks, uname, gname,
                                                      size, mtime, basename)
 
     def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True):
@@ -1288,18 +1260,18 @@ class AbstractedFS:
                     else:
                         type = 'type=dir;'
                 if 'perm' in facts:
-                    perm = 'perm=%s;' % permdir
+                    perm = 'perm=%s;' %permdir
             else:
                 if 'type' in facts:
                     type = 'type=file;'
                 if 'perm' in facts:
-                    perm = 'perm=%s;' % permfile
+                    perm = 'perm=%s;' %permfile
             if 'size' in facts:
-                size = 'size=%s;' % st.st_size  # file size
+                size = 'size=%s;' %st.st_size  # file size
             # last modification time
             if 'modify' in facts:
                 try:
-                    modify = 'modify=%s;' % time.strftime("%Y%m%d%H%M%S",
+                    modify = 'modify=%s;' %time.strftime("%Y%m%d%H%M%S",
                                            time.localtime(st.st_mtime))
                 except ValueError:
                     # stat.st_mtime could fail (-1) if last mtime is too old
@@ -1307,17 +1279,17 @@ class AbstractedFS:
             if 'create' in facts:
                 # on Windows we can provide also the creation time
                 try:
-                    create = 'create=%s;' % time.strftime("%Y%m%d%H%M%S",
+                    create = 'create=%s;' %time.strftime("%Y%m%d%H%M%S",
                                            time.localtime(st.st_ctime))
                 except ValueError:
                     create = ""
             # UNIX only
             if 'unix.mode' in facts:
-                mode = 'unix.mode=%s;' % oct(st.st_mode & 0777)
+                mode = 'unix.mode=%s;' %oct(st.st_mode & 0777)
             if 'unix.uid' in facts:
-                uid = 'unix.uid=%s;' % st.st_uid
+                uid = 'unix.uid=%s;' %st.st_uid
             if 'unix.gid' in facts:
-                gid = 'unix.gid=%s;' % st.st_gid
+                gid = 'unix.gid=%s;' %st.st_gid
             # We provide unique fact (see RFC-3659, chapter 7.5.2) on
             # posix platforms only; we get it by mixing st_dev and
             # st_ino values which should be enough for granting an
@@ -1327,14 +1299,19 @@ class AbstractedFS:
             # platforms should use some platform-specific method (e.g.
             # on Windows NTFS filesystems MTF records could be used).
             if 'unique' in facts:
-                unique = "unique=%x%x;" % (st.st_dev, st.st_ino)
+                unique = "unique=%x%x;" %(st.st_dev, st.st_ino)
 
-            yield "%s%s%s%s%s%s%s%s%s %s\r\n" % (type, size, perm, modify, create,
+            yield "%s%s%s%s%s%s%s%s%s %s\r\n" %(type, size, perm, modify, create,
                                                 mode, uid, gid, unique, basename)
 
 
 # --- FTP
 
+class FTPExceptionSent(Exception):
+    """An FTP exception that FTPHandler has processed
+    """
+    pass
+
 class FTPHandler(asynchat.async_chat):
     """Implements the FTP server Protocol Interpreter (see RFC-959),
     handling commands received from the client on the control channel.
@@ -1398,7 +1375,7 @@ class FTPHandler(asynchat.async_chat):
     abstracted_fs = AbstractedFS
 
     # session attributes (explained in the docstring)
-    banner = "pyftpdlib %s ready." % __ver__
+    banner = "pyftpdlib %s ready." %__ver__
     max_login_attempts = 3
     permit_foreign_addresses = False
     permit_privileged_ports = False
@@ -1414,7 +1391,7 @@ class FTPHandler(asynchat.async_chat):
         """
         try:
             asynchat.async_chat.__init__(self, conn=conn) # python2.5
-        except TypeError, e:
+        except TypeError:
             asynchat.async_chat.__init__(self, sock=conn) # python2.6
         self.server = server
         self.remote_ip, self.remote_port = self.socket.getpeername()[:2]
@@ -1435,15 +1412,20 @@ class FTPHandler(asynchat.async_chat):
         self.__in_dtp_queue = None
         self.__out_dtp_queue = None
 
+        self.__errno_responses = {
+                errno.EPERM: 553,
+                errno.EINVAL: 504,
+                errno.ENOENT: 550,
+                errno.EREMOTE: 450,
+                errno.EEXIST: 521,
+                }
+
         # mlsx facts attributes
         self.current_facts = ['type', 'perm', 'size', 'modify']
-        if os.name == 'posix':
-            self.current_facts.append('unique')
+        self.current_facts.append('unique')
         self.available_facts = self.current_facts[:]
-        if pwd and grp:
-            self.available_facts += ['unix.mode', 'unix.uid', 'unix.gid']
-        if os.name == 'nt':
-            self.available_facts.append('create')
+        self.available_facts += ['unix.mode', 'unix.uid', 'unix.gid']
+        self.available_facts.append('create')
 
         # dtp attributes
         self.data_server = None
@@ -1461,15 +1443,15 @@ class FTPHandler(asynchat.async_chat):
         channel.
         """
         if len(self.banner) <= 75:
-            self.respond("220 %s" % str(self.banner))
+            self.respond("220 %s" %str(self.banner))
         else:
-            self.push('220-%s\r\n' % str(self.banner))
+            self.push('220-%s\r\n' %str(self.banner))
             self.respond('220 ')
 
     def handle_max_cons(self):
         """Called when limit for maximum number of connections is reached."""
         msg = "Too many connections. Service temporary unavailable."
-        self.respond("421 %s" % msg)
+        self.respond("421 %s" %msg)
         self.log(msg)
         # If self.push is used, data could not be sent immediately in
         # which case a new "loop" will occur exposing us to the risk of
@@ -1483,7 +1465,7 @@ class FTPHandler(asynchat.async_chat):
     def handle_max_cons_per_ip(self):
         """Called when too many clients are connected from the same IP."""
         msg = "Too many connections from the same IP address."
-        self.respond("421 %s" % msg)
+        self.respond("421 %s" %msg)
         self.log(msg)
         self.close_when_done()
 
@@ -1503,21 +1485,21 @@ class FTPHandler(asynchat.async_chat):
         buflimit = 2048
         if self.in_buffer_len > buflimit:
             self.respond('500 Command too long.')
-            self.log('Command received exceeded buffer limit of %s.' % (buflimit))
+            self.log('Command received exceeded buffer limit of %s.' %(buflimit))
             self.in_buffer = []
             self.in_buffer_len = 0
 
     # commands accepted before authentication
-    unauth_cmds = ('FEAT', 'HELP', 'NOOP', 'PASS', 'QUIT', 'STAT', 'SYST', 'USER')
+    unauth_cmds = ('FEAT','HELP','NOOP','PASS','QUIT','STAT','SYST','USER')
 
     # commands needing an argument
-    arg_cmds = ('ALLO', 'APPE', 'DELE', 'EPRT', 'MDTM', 'MODE', 'MKD', 'OPTS', 'PORT',
-                'REST', 'RETR', 'RMD', 'RNFR', 'RNTO', 'SIZE', 'STOR', 'STRU',
-                'TYPE', 'USER', 'XMKD', 'XRMD')
+    arg_cmds = ('ALLO','APPE','DELE','EPRT','MDTM','MODE','MKD','OPTS','PORT',
+                'REST','RETR','RMD','RNFR','RNTO','SIZE', 'STOR','STRU',
+                'TYPE','USER','XMKD','XRMD')
 
     # commands needing no argument
-    unarg_cmds = ('ABOR', 'CDUP', 'FEAT', 'NOOP', 'PASV', 'PWD', 'QUIT', 'REIN',
-                  'SYST', 'XCUP', 'XPWD')
+    unarg_cmds = ('ABOR','CDUP','FEAT','NOOP','PASV','PWD','QUIT','REIN',
+                  'SYST','XCUP','XPWD')
 
     def found_terminator(self):
         r"""Called when the incoming data stream matches the \r\n
@@ -1539,9 +1521,9 @@ class FTPHandler(asynchat.async_chat):
             arg = ""
 
         if cmd != 'PASS':
-            self.logline("<== %s" % line)
+            self.logline("<== %s" %line)
         else:
-            self.logline("<== %s %s" % (line.split(' ')[0], '*' * 6))
+            self.logline("<== %s %s" %(line.split(' ')[0], '*' * 6))
 
         # let's check if user provided an argument for those commands
         # needing one
@@ -1570,7 +1552,7 @@ class FTPHandler(asynchat.async_chat):
             elif cmd in proto_cmds:
                 self.respond("530 Log in with USER and PASS first.")
             else:
-                self.respond('500 Command "%s" not understood.' % line)
+                self.respond('500 Command "%s" not understood.' %line)
 
         # provide full command set
         elif (self.authenticated) and (cmd in proto_cmds):
@@ -1587,30 +1569,12 @@ class FTPHandler(asynchat.async_chat):
                 self.ftp_STAT("")
             # unknown command
             else:
-                self.respond('500 Command "%s" not understood.' % line)
+                self.respond('500 Command "%s" not understood.' %line)
 
     def __check_path(self, cmd, line):
         """Check whether a path is valid."""
-        # For the following commands we have to make sure that the real
-        # path destination belongs to the user's root directory.
-        # If provided path is a symlink we follow its final destination
-        # to do so.
-        if cmd in ('APPE', 'CWD', 'DELE', 'MDTM', 'NLST', 'MLSD', 'MLST', 'RETR',
-                   'RMD', 'SIZE', 'STOR', 'XCWD', 'XRMD'):
-            datacr = None
-            datacr = self.fs.get_cr(line)
-            try:
-                if not self.fs.validpath(self.fs.ftp2fs(line, datacr)):
-                    line = self.fs.ftpnorm(line)
-                    err = '"%s" points to a path which is outside ' \
-                          "the user's root directory" % line
-                    self.respond("550 %s." % err)
-                    self.log('FAIL %s "%s". %s.' % (cmd, line, err))
-                    self.fs.close_cr(datacr)
-                    return False
-            except:
-                pass
-            self.fs.close_cr(datacr)
+
+        # Always true, we will only check later, once we have a cursor
         return True
 
     def __check_perm(self, cmd, line, datacr):
@@ -1623,18 +1587,19 @@ class FTPHandler(asynchat.async_chat):
                'RNFR':'f',
                'MKD':'m', 'XMKD':'m',
                'STOR':'w'}
+        raise NotImplementedError
         if cmd in map:
             if cmd == 'STAT' and not line:
                 return True
             perm = map[cmd]
-            if not line and (cmd in ('LIST', 'NLST', 'MLSD')):
+            if not line and (cmd in ('LIST','NLST','MLSD')):
                 path = self.fs.ftp2fs(self.fs.cwd, datacr)
             else:
                 path = self.fs.ftp2fs(line, datacr)
             if not self.authorizer.has_perm(self.username, perm, path):
                 self.log('FAIL %s "%s". Not enough privileges.' \
-                         % (cmd, self.fs.ftpnorm(line)))
-                self.respond("550 Can't %s. Not enough privileges." % cmd)
+                         %(cmd, self.fs.ftpnorm(line)))
+                self.respond("550 Can't %s. Not enough privileges." %cmd)
                 return False
         return True
 
@@ -1773,12 +1738,12 @@ class FTPHandler(asynchat.async_chat):
 
     def log(self, msg):
         """Log a message, including additional identifying session data."""
-        log("[%s]@%s:%s %s" % (self.username, self.remote_ip,
+        log("[%s]@%s:%s %s" %(self.username, self.remote_ip,
                               self.remote_port, msg))
 
     def logline(self, msg):
         """Log a line including additional indentifying session data."""
-        logline("%s:%s %s" % (self.remote_ip, self.remote_port, msg))
+        logline("%s:%s %s" %(self.remote_ip, self.remote_port, msg))
 
     def flush_account(self):
         """Flush account information by clearing attributes that need
@@ -1813,6 +1778,58 @@ class FTPHandler(asynchat.async_chat):
 
         # --- connection
 
+    def try_as_current_user(self, function, args=None, kwargs=None, line=None, errno_resp=None):
+        """run function as current user, auto-respond in exceptions
+           @param args,kwargs the arguments, in list and dict respectively
+           @param errno_resp a dictionary of responses to IOError, OSError
+        """
+        if errno_resp:
+            eresp = self.__errno_responses.copy()
+            eresp.update(errno_resp)
+        else:
+            eresp = self.__errno_responses
+
+        uline = ''
+        if line:
+            uline = ' "%s"' % _to_unicode(line)
+        try:
+            if args is None:
+                args = ()
+            if kwargs is None:
+                kwargs = {}
+            return self.run_as_current_user(function, *args, **kwargs)
+        except NotImplementedError, err:
+            cmdname = function.__name__
+            why = err.args[0] or 'Not implemented'
+            self.log('FAIL %s() not implemented:  %s.' %(cmdname, why))
+            self.respond('502 %s.' %why)
+            raise FTPExceptionSent(why)
+        except EnvironmentError, err:
+            cmdname = function.__name__
+            try:
+                logline(traceback.format_exc())
+            except Exception:
+                pass
+            ret_code = eresp.get(err.errno, '451')
+            why = (err.strerror) or 'Error in command'
+            self.log('FAIL %s() %s errno=%s:  %s.' %(cmdname, uline, err.errno, why))
+            self.respond('%s %s.' % (str(ret_code), why))
+
+            raise FTPExceptionSent(why)
+        except Exception, err:
+            cmdname = function.__name__
+            try:
+                logerror(traceback.format_exc())
+            except Exception:
+                pass
+            why = (err.args and err.args[0]) or 'Exception'
+            self.log('FAIL %s() %s Exception:  %s.' %(cmdname, uline, why))
+            self.respond('451 %s.' % why)
+            raise FTPExceptionSent(why)
+
+    def get_crdata2(self, *args, **kwargs):
+        return self.try_as_current_user(self.fs.get_crdata, args, kwargs, line=args[0])
+
     def _make_eport(self, ip, port):
         """Establish an active data channel with remote client which
         issued a PORT or EPRT command.
@@ -1823,7 +1840,7 @@ class FTPHandler(asynchat.async_chat):
         if not self.permit_foreign_addresses:
             if ip != self.remote_ip:
                 self.log("Rejected data connection to foreign address %s:%s."
-                         % (ip, port))
+                         %(ip, port))
                 self.respond("501 Can't connect to a foreign address.")
                 return
 
@@ -1831,7 +1848,7 @@ class FTPHandler(asynchat.async_chat):
         # to privileged ports (< 1024) for security reasons.
         if not self.permit_privileged_ports:
             if port < 1024:
-                self.log('PORT against the privileged port "%s" refused.' % port)
+                self.log('PORT against the privileged port "%s" refused.' %port)
                 self.respond("501 Can't connect over a privileged port.")
                 return
 
@@ -1847,7 +1864,7 @@ class FTPHandler(asynchat.async_chat):
         if self.server.max_cons:
             if len(self._map) >= self.server.max_cons:
                 msg = "Too many connections. Can't open data channel."
-                self.respond("425 %s" % msg)
+                self.respond("425 %s" %msg)
                 self.log(msg)
                 return
 
@@ -1873,7 +1890,7 @@ class FTPHandler(asynchat.async_chat):
         if self.server.max_cons:
             if len(self._map) >= self.server.max_cons:
                 msg = "Too many connections. Can't open data channel."
-                self.respond("425 %s" % msg)
+                self.respond("425 %s" %msg)
                 self.log(msg)
                 return
 
@@ -1899,7 +1916,7 @@ class FTPHandler(asynchat.async_chat):
             assert len(addr) == 6
             for x in addr[:4]:
                 assert 0 <= x <= 255
-            ip = '%d.%d.%d.%d' % tuple(addr[:4])
+            ip = '%d.%d.%d.%d' %tuple(addr[:4])
             port = (addr[4] * 256) + addr[5]
             assert 0 <= port <= 65535
         except (AssertionError, ValueError, OverflowError):
@@ -1936,7 +1953,7 @@ class FTPHandler(asynchat.async_chat):
                     assert len(octs) == 4
                     for x in octs:
                         assert 0 <= x <= 255
-                except (AssertionError, ValueError, OverflowError), err:
+                except (AssertionError, ValueError, OverflowError):
                     self.respond("501 Invalid EPRT format.")
                 else:
                     self._make_eport(ip, port)
@@ -1975,7 +1992,7 @@ class FTPHandler(asynchat.async_chat):
         # which IPv6 address to use for binding the socket?
         # Unfortunately RFC-2428 does not provide satisfing information
         # on how to do that.  The assumption is that we don't have any way
-        # to know wich address to use, hence we just use the same address
+        # to know which address to use, hence we just use the same address
         # family used on the control connection.
         if not line:
             self._make_epasv(extmode=True)
@@ -2010,9 +2027,9 @@ class FTPHandler(asynchat.async_chat):
         else:
             msg_quit = "Goodbye."
         if len(msg_quit) <= 75:
-            self.respond("221 %s" % msg_quit)
+            self.respond("221 %s" %msg_quit)
         else:
-            self.push("221-%s\r\n" % msg_quit)
+            self.push("221-%s\r\n" %msg_quit)
             self.respond("221 ")
 
         if not self.data_channel:
@@ -2032,56 +2049,58 @@ class FTPHandler(asynchat.async_chat):
         # - Some older FTP clients erroneously issue /bin/ls-like LIST
         #   formats in which case we fall back on cwd as default.
         if not line or line.lower() in ('-a', '-l', '-al', '-la'):
-            line = self.fs.cwd
+            line = ''
+        datacr = None
+        try:
+            datacr = self.get_crdata2(line, mode='list')
+            iterator = self.try_as_current_user(self.fs.get_list_dir, (datacr,))
+        except FTPExceptionSent:
+            self.fs.close_cr(datacr)
+            return
+
         try:
-            data = None
-            data = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, data)
-            line = self.fs.ftpnorm(line)
-            iterator = self.run_as_current_user(self.fs.get_list_dir, path)
-        except OSError, err:
-            self.fs.close_cr(data)
-            why = _strerror(err)
-            self.log('FAIL LIST "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
-        else:
-            self.fs.close_cr(data)
             self.log('OK LIST "%s". Transfer starting.' % line)
             producer = BufferedIteratorProducer(iterator)
             self.push_dtp_data(producer, isproducer=True)
+        finally:
+            self.fs.close_cr(datacr)
+
 
     def ftp_NLST(self, line):
         """Return a list of files in the specified directory in a
         compact form to the client.
         """
         if not line:
-            line = self.fs.cwd
+            line = ''
+
+        datacr = None
         try:
-            data = None
-            data = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, data)
-            line = self.fs.ftpnorm(line)
-            if self.fs.isdir(path):
-                listing = self.run_as_current_user(self.fs.listdir, path)
-                listing = map(lambda x:os.path.split(x.path)[1], listing)
+            datacr = self.get_crdata2(line, mode='list')
+            if not datacr:
+                datacr = ( None, None, None )
+            if self.fs.isdir(datacr[1]):
+                nodelist = self.try_as_current_user(self.fs.listdir, (datacr,))
             else:
                 # if path is a file we just list its name
-                self.fs.lstat(path)  # raise exc in case of problems
-                basedir, filename = os.path.split(line)
-                listing = [filename]
-        except OSError, err:
-            self.fs.close_cr(data)
-            why = _strerror(err)
-            self.log('FAIL NLST "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
-        else:
-            self.fs.close_cr(data)
-            data = ''
-            if listing:
-                listing.sort()
-                data = '\r\n'.join(listing) + '\r\n'
-            self.log('OK NLST "%s". Transfer starting.' % line)
-            self.push_dtp_data(data)
+                nodelist = [datacr[1],]
+
+            listing = []
+            for nl in nodelist:
+                if isinstance(nl.path, (list, tuple)):
+                    listing.append(nl.path[-1])
+                else:
+                    listing.append(nl.path)    # assume string
+        except FTPExceptionSent:
+            self.fs.close_cr(datacr)
+            return
+
+        self.fs.close_cr(datacr)
+        data = ''
+        if listing:
+            listing.sort()
+            data =  ''.join([ _to_decode(x) + '\r\n' for x in listing ])
+        self.log('OK NLST "%s". Transfer starting.' %line)
+        self.push_dtp_data(data)
 
         # --- MLST and MLSD commands
 
@@ -2096,29 +2115,24 @@ class FTPHandler(asynchat.async_chat):
         """
         # if no argument, fall back on cwd as default
         if not line:
-            line = self.fs.cwd
+            line = ''
+        datacr = None
         try:
-            datacr = None
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
-            basedir, basename = os.path.split(path)
+            datacr = self.get_crdata2(line, mode='list')
             perms = self.authorizer.get_perms(self.username)
-            iterator = self.run_as_current_user(self.fs.format_mlsx, basedir,
-                       [basename], perms, self.current_facts, ignore_err=False)
+            iterator = self.try_as_current_user(self.fs.format_mlsx, (datacr[0], datacr[1].parent,
+                       [datacr[1],], perms, self.current_facts), {'ignore_err':False})
             data = ''.join(iterator)
-        except OSError, err:
+        except FTPExceptionSent:
             self.fs.close_cr(datacr)
-            why = _strerror(err)
-            self.log('FAIL MLST "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
+            return
         else:
             self.fs.close_cr(datacr)
             # since TVFS is supported (see RFC-3659 chapter 6), a fully
             # qualified pathname should be returned
-            data = data.split(' ')[0] + ' %s\r\n' % line
+            data = data.split(' ')[0] + ' %s\r\n' %line
             # response is expected on the command channel
-            self.push('250-Listing "%s":\r\n' % line)
+            self.push('250-Listing "%s":\r\n' %line)
             # the fact set must be preceded by a space
             self.push(' ' + data)
             self.respond('250 End MLST.')
@@ -2129,54 +2143,40 @@ class FTPHandler(asynchat.async_chat):
         """
         # if no argument, fall back on cwd as default
         if not line:
-            line = self.fs.cwd
+            line = ''
+
+        datacr = None
         try:
-            datacr = None
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
+            datacr = self.get_crdata2(line, mode='list')
             # RFC-3659 requires 501 response code if path is not a directory
-            if not self.fs.isdir(path):
+            if not self.fs.isdir(datacr[1]):
                 err = 'No such directory'
-                self.log('FAIL MLSD "%s". %s.' % (line, err))
-                self.respond("501 %s." % err)
+                self.log('FAIL MLSD "%s". %s.' %(line, err))
+                self.respond("501 %s." %err)
                 return
-            listing = self.run_as_current_user(self.fs.listdir, path)
-        except OSError, err:
+            listing = self.try_as_current_user(self.fs.listdir, (datacr,))
+        except FTPExceptionSent:
             self.fs.close_cr(datacr)
-            why = _strerror(err)
-            self.log('FAIL MLSD "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
+            return
         else:
             self.fs.close_cr(datacr)
             perms = self.authorizer.get_perms(self.username)
-            iterator = self.fs.format_mlsx(path, listing, perms,
+            iterator = self.fs.format_mlsx(datacr[0], datacr[1], listing, perms,
                        self.current_facts)
             producer = BufferedIteratorProducer(iterator)
-            self.log('OK MLSD "%s". Transfer starting.' % line)
+            self.log('OK MLSD "%s". Transfer starting.' %line)
             self.push_dtp_data(producer, isproducer=True)
 
     def ftp_RETR(self, line):
         """Retrieve the specified file (transfer from the server to the
         client)
         """
+        datacr = None
         try:
-            datacr = None
-            datacr = self.fs.get_cr(line)
-            file = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
-            fd = self.run_as_current_user(self.fs.open, file, 'rb')
-        except OSError, err:
-            self.fs.close_cr(datacr)
-            why = _strerror(err)
-            self.log('FAIL RETR "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
-            return
-        except IOError, err:
+            datacr = self.get_crdata2(line, mode='file')
+            fd = self.try_as_current_user(self.fs.open, (datacr, 'rb'))
+        except FTPExceptionSent:
             self.fs.close_cr(datacr)
-            why = _strerror(err)
-            self.log('FAIL RETR "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
             return
 
         if self.restart_position:
@@ -2187,7 +2187,7 @@ class FTPHandler(asynchat.async_chat):
             # the REST.
             ok = 0
             try:
-                assert not self.restart_position > self.fs.getsize(file)
+                assert not self.restart_position > self.fs.getsize(datacr)
                 fd.seek(self.restart_position)
                 ok = 1
             except AssertionError:
@@ -2196,11 +2196,11 @@ class FTPHandler(asynchat.async_chat):
                 why = _strerror(err)
             self.restart_position = 0
             if not ok:
-                self.respond('554 %s' % why)
-                self.log('FAIL RETR "%s". %s.' % (line, why))
+                self.respond('554 %s' %why)
+                self.log('FAIL RETR "%s". %s.' %(line, why))
                 self.fs.close_cr(datacr)
                 return
-        self.log('OK RETR "%s". Download starting.' % line)
+        self.log('OK RETR "%s". Download starting.' %line)
         producer = FileProducer(fd, self.current_type)
         self.push_dtp_data(producer, isproducer=True, file=fd)
         self.fs.close_cr(datacr)
@@ -2217,30 +2217,15 @@ class FTPHandler(asynchat.async_chat):
         else:
             cmd = 'STOR'
 
-        line = self.fs.ftpnorm(line)
-        basedir, basename = os.path.split(line)
-
         datacr = None
         try:
-            datacr = self.fs.get_cr(line)
-            file = self.fs.ftp2fs(basedir, datacr)
-
-        except OSError, err:
+            datacr = self.get_crdata2(line,mode='create')
+            if self.restart_position:
+                mode = 'r+'
+            fd = self.try_as_current_user(self.fs.create, (datacr, datacr[2], mode + 'b'))
+            assert fd
+        except FTPExceptionSent:
             self.fs.close_cr(datacr)
-            why = _strerror(err)
-            self.log('FAIL %s "%s". %s.' % (cmd, line, why))
-            self.respond('550 %s.' % why)
-            return
-
-        if self.restart_position:
-            mode = 'r+'
-        try:
-            fd = self.run_as_current_user(self.fs.create, file, basename, mode + 'b')
-        except IOError, err:
-            self.fs.close_cr(datacr)
-            why = _strerror(err)
-            self.log('FAIL %s "%s". %s.' % (cmd, line, why))
-            self.respond('550 %s.' % why)
             return
 
         if self.restart_position:
@@ -2251,7 +2236,7 @@ class FTPHandler(asynchat.async_chat):
             # specified in the REST.
             ok = 0
             try:
-                assert not self.restart_position > self.fs.getsize(self.fs.ftp2fs(line, datacr))
+                assert not self.restart_position > self.fs.getsize(datacr)
                 fd.seek(self.restart_position)
                 ok = 1
             except AssertionError:
@@ -2261,11 +2246,11 @@ class FTPHandler(asynchat.async_chat):
             self.restart_position = 0
             if not ok:
                 self.fs.close_cr(datacr)
-                self.respond('554 %s' % why)
-                self.log('FAIL %s "%s". %s.' % (cmd, line, why))
+                self.respond('554 %s' %why)
+                self.log('FAIL %s "%s". %s.' %(cmd, line, why))
                 return
 
-        self.log('OK %s "%s". Upload starting.' % (cmd, line))
+        self.log('OK %s "%s". Upload starting.' %(cmd, line))
         if self.data_channel:
             self.respond("125 Data connection already open. Transfer starting.")
             self.data_channel.file_obj = fd
@@ -2293,21 +2278,21 @@ class FTPHandler(asynchat.async_chat):
             self.respond("450 Can't STOU while REST request is pending.")
             return
 
-        datacr = None
-        datacr = self.fs.get_cr(line)
 
         if line:
-            line = self.fs.ftpnorm(line)
-            basedir, prefix = os.path.split(line)
-            basedir = self.fs.ftp2fs(basedir, datacr)
-            #prefix = prefix + '.'
+            datacr = self.get_crdata2(line, mode='create')
+            # TODO
         else:
+            # TODO
             basedir = self.fs.ftp2fs(self.fs.cwd, datacr)
             prefix = 'ftpd.'
         try:
-            fd = self.run_as_current_user(self.fs.mkstemp, prefix=prefix,
-                                          dir=basedir)
-        except IOError, err:
+            fd = self.try_as_current_user(self.fs.mkstemp, kwargs={'prefix':prefix,
+                                          'dir': basedir}, line=line )
+        except FTPExceptionSent:
+            self.fs.close_cr(datacr)
+            return
+        except IOError, err: # TODO
             # hitted the max number of tries to find out file with
             # unique name
             if err.errno == errno.EEXIST:
@@ -2315,27 +2300,27 @@ class FTPHandler(asynchat.async_chat):
             # something else happened
             else:
                 why = _strerror(err)
-            self.respond("450 %s." % why)
-            self.log('FAIL STOU "%s". %s.' % (self.fs.ftpnorm(line), why))
+            self.respond("450 %s." %why)
+            self.log('FAIL STOU "%s". %s.' %(self.fs.ftpnorm(line), why))
             self.fs.close_cr(datacr)
             return
 
         filename = line
         if not self.authorizer.has_perm(self.username, 'w', filename):
             self.log('FAIL STOU "%s". Not enough privileges'
-                     % self.fs.ftpnorm(line))
+                     %self.fs.ftpnorm(line))
             self.respond("550 Can't STOU: not enough privileges.")
             self.fs.close_cr(datacr)
             return
 
         # now just acts like STOR except that restarting isn't allowed
-        self.log('OK STOU "%s". Upload starting.' % filename)
+        self.log('OK STOU "%s". Upload starting.' %filename)
         if self.data_channel:
-            self.respond("125 FILE: %s" % filename)
+            self.respond("125 FILE: %s" %filename)
             self.data_channel.file_obj = fd
             self.data_channel.enable_receiving(self.current_type)
         else:
-            self.respond("150 FILE: %s" % filename)
+            self.respond("150 FILE: %s" %filename)
             self.__in_dtp_queue = fd
         self.fs.close_cr(datacr)
 
@@ -2358,8 +2343,8 @@ class FTPHandler(asynchat.async_chat):
             self.respond("501 Invalid parameter.")
         else:
             self.respond("350 Restarting at position %s. " \
-                        "Now use RETR/STOR for resuming." % marker)
-            self.log("OK REST %s." % marker)
+                        "Now use RETR/STOR for resuming." %marker)
+            self.log("OK REST %s." %marker)
             self.restart_position = marker
 
     def ftp_ABOR(self, line):
@@ -2421,8 +2406,8 @@ class FTPHandler(asynchat.async_chat):
             # login sequence again.
             self.flush_account()
             msg = 'Previous account information was flushed'
-            self.log('OK USER "%s". %s.' % (line, msg))
-            self.respond('331 %s, send password.' % msg)
+            self.log('OK USER "%s". %s.' %(line, msg))
+            self.respond('331 %s, send password.' %msg)
         self.username = line
 
     def ftp_PASS(self, line):
@@ -2441,18 +2426,18 @@ class FTPHandler(asynchat.async_chat):
             or self.authorizer.validate_authentication(self.username, line):
                 msg_login = self.authorizer.get_msg_login(self.username)
                 if len(msg_login) <= 75:
-                    self.respond('230 %s' % msg_login)
+                    self.respond('230 %s' %msg_login)
                 else:
-                    self.push("230-%s\r\n" % msg_login)
+                    self.push("230-%s\r\n" %msg_login)
                     self.respond("230 ")
 
                 self.authenticated = True
                 self.password = line
                 self.attempted_logins = 0
                 self.fs.root = self.authorizer.get_home_dir(self.username)
-                self.fs.username = self.username
-                self.fs.password = line
-                self.log("User %s logged in." % self.username)
+                self.fs.username=self.username
+                self.fs.password=line
+                self.log("User %s logged in." %self.username)
             else:
                 self.attempted_logins += 1
                 if self.attempted_logins >= self.max_login_attempts:
@@ -2460,7 +2445,7 @@ class FTPHandler(asynchat.async_chat):
                     self.close()
                 else:
                     self.respond("530 Authentication failed.")
-                self.log('Authentication failed (user: "%s").' % self.username)
+                self.log('Authentication failed (user: "%s").' %self.username)
                 self.username = ""
 
         # wrong username
@@ -2468,7 +2453,7 @@ class FTPHandler(asynchat.async_chat):
             self.attempted_logins += 1
             if self.attempted_logins >= self.max_login_attempts:
                 self.log('Authentication failed: unknown username "%s".'
-                            % self.username)
+                            %self.username)
                 self.respond("530 Maximum login attempts. Disconnecting.")
                 self.close()
             elif self.username.lower() == 'anonymous':
@@ -2477,7 +2462,7 @@ class FTPHandler(asynchat.async_chat):
             else:
                 self.respond("530 Authentication failed.")
                 self.log('Authentication failed: unknown username "%s".'
-                            % self.username)
+                            %self.username)
                 self.username = ""
 
     def ftp_REIN(self, line):
@@ -2500,33 +2485,25 @@ class FTPHandler(asynchat.async_chat):
 
     def ftp_PWD(self, line):
         """Return the name of the current working directory to the client."""
-        self.respond('257 "%s" is the current directory.' % self.fs.cwd)
+        cwd = self.fs.get_cwd()
+        self.respond('257 "%s" is the current directory.' % cwd)
 
     def ftp_CWD(self, line):
         """Change the current working directory."""
-        # TODO: a lot of FTP servers go back to root directory if no
+        # check: a lot of FTP servers go back to root directory if no
         # arg is provided but this is not specified in RFC-959.
         # Search for official references about this behaviour.
-        if not line:
-            line = '/'
         datacr = None
         try:
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            self.run_as_current_user(self.fs.chdir, path)
-        except OSError, err:
-            if err.errno == 2:
-                why = 'Authentication Required or Failed'
-                self.log('FAIL CWD "%s". %s.' % (self.fs.ftpnorm(line), why))
-                self.respond('530 %s.' % why)
-            else:
-                why = _strerror(err)
-                self.log('FAIL CWD "%s". %s.' % (self.fs.ftpnorm(line), why))
-                self.respond('550 %s.' % why)
-        else:
-            self.log('OK CWD "%s".' % self.fs.cwd)
-            self.respond('250 "%s" is the current directory.' % self.fs.cwd)
-        self.fs.close_cr(datacr)
+            datacr = self.get_crdata2(line,'cwd')
+            self.try_as_current_user(self.fs.chdir, (datacr,), line=line, errno_resp={2: 530})
+            cwd = self.fs.get_cwd()
+            self.log('OK CWD "%s".' % cwd)
+            self.respond('250 "%s" is the current directory.' % cwd)
+        except FTPExceptionSent:
+            return
+        finally:
+            self.fs.close_cr(datacr)
 
     def ftp_CDUP(self, line):
         """Change into the parent directory."""
@@ -2554,23 +2531,14 @@ class FTPHandler(asynchat.async_chat):
         """
         datacr = None
         try:
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
-            if self.fs.isdir(path):
-                why = "%s is not retrievable" % line
-                self.log('FAIL SIZE "%s". %s.' % (line, why))
-                self.respond("550 %s." % why)
-                self.fs.close_cr(datacr)
-                return
-            size = self.run_as_current_user(self.fs.getsize, path)
-        except OSError, err:
-            why = _strerror(err)
-            self.log('FAIL SIZE "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
+            datacr = self.get_crdata2(line, mode='file')
+            size = self.try_as_current_user(self.fs.getsize,(datacr,), line=line)
+        except FTPExceptionSent:
+            self.fs.close_cr(datacr)
+            return
         else:
-            self.respond("213 %s" % size)
-            self.log('OK SIZE "%s".' % line)
+            self.respond("213 %s" %size)
+            self.log('OK SIZE "%s".' %line)
         self.fs.close_cr(datacr)
 
     def ftp_MDTM(self, line):
@@ -2578,42 +2546,35 @@ class FTPHandler(asynchat.async_chat):
         3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659.
         """
         datacr = None
+
         try:
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
-            if not self.fs.isfile(self.fs.realpath(path)):
-                why = "%s is not retrievable" % line
-                self.log('FAIL MDTM "%s". %s.' % (line, why))
-                self.respond("550 %s." % why)
-                self.fs.close_cr(datacr)
-                return
-            lmt = self.run_as_current_user(self.fs.getmtime, path)
-        except OSError, err:
-            why = _strerror(err)
-            self.log('FAIL MDTM "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
-        else:
+            if line.find('/', 1) < 0:
+                # root or db, just return local
+                lmt = None
+            else:
+                datacr = self.get_crdata2(line)
+                if not datacr:
+                    raise IOError(errno.ENOENT, "%s is not retrievable" %line)
+
+                lmt = self.try_as_current_user(self.fs.getmtime, (datacr,), line=line)
             lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt))
-            self.respond("213 %s" % lmt)
-            self.log('OK MDTM "%s".' % line)
-        self.fs.close_cr(datacr)
+            self.respond("213 %s" %lmt)
+            self.log('OK MDTM "%s".' %line)
+        except FTPExceptionSent:
+            return
+        finally:
+            self.fs.close_cr(datacr)
 
     def ftp_MKD(self, line):
         """Create the specified directory."""
-        datacr = None
-        line = self.fs.ftpnorm(line)
-        basedir, basename = os.path.split(line)
         try:
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(basedir, datacr)
-            self.run_as_current_user(self.fs.mkdir, path, basename)
-        except OSError, err:
-            why = _strerror(err)
-            self.log('FAIL MKD "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
+            datacr = self.get_crdata2(line, mode='create')
+            self.try_as_current_user(self.fs.mkdir, (datacr, datacr[2]), line=line)
+        except FTPExceptionSent:
+            self.fs.close_cr(datacr)
+            return
         else:
-            self.log('OK MKD "%s".' % line)
+            self.log('OK MKD "%s".' %line)
             self.respond("257 Directory created.")
         self.fs.close_cr(datacr)
 
@@ -2621,40 +2582,30 @@ class FTPHandler(asynchat.async_chat):
         """Remove the specified directory."""
         datacr = None
         try:
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
-            if self.fs.realpath(path) == self.fs.realpath(self.fs.root):
+            datacr = self.get_crdata2(line, mode='delete')
+            if not datacr[1]:
                 msg = "Can't remove root directory."
-                self.respond("550 %s" % msg)
-                self.log('FAIL MKD "/". %s' % msg)
+                self.respond("553 %s" %msg)
+                self.log('FAIL MKD "/". %s' %msg)
                 self.fs.close_cr(datacr)
                 return
-            self.run_as_current_user(self.fs.rmdir, path)
-        except OSError, err:
-            why = _strerror(err)
-            self.log('FAIL RMD "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
-        else:
-            self.log('OK RMD "%s".' % line)
+            self.try_as_current_user(self.fs.rmdir, (datacr,), line=line)
+            self.log('OK RMD "%s".' %line)
             self.respond("250 Directory removed.")
+        except FTPExceptionSent:
+            pass
         self.fs.close_cr(datacr)
 
     def ftp_DELE(self, line):
         """Delete the specified file."""
         datacr = None
         try:
-            datacr = self.fs.get_cr(line)
-            path = self.fs.ftp2fs(line, datacr)
-            line = self.fs.ftpnorm(line)
-            self.run_as_current_user(self.fs.remove, path)
-        except OSError, err:
-            why = _strerror(err)
-            self.log('FAIL DELE "%s". %s.' % (line, why))
-            self.respond('550 %s.' % why)
-        else:
-            self.log('OK DELE "%s".' % line)
+            datacr = self.get_crdata2(line, mode='delete')
+            self.try_as_current_user(self.fs.remove, (datacr,), line=line)
+            self.log('OK DELE "%s".' %line)
             self.respond("250 File removed.")
+        except FTPExceptionSent:
+            pass
         self.fs.close_cr(datacr)
 
     def ftp_RNFR(self, line):
@@ -2662,18 +2613,16 @@ class FTPHandler(asynchat.async_chat):
         here, see RNTO command)"""
         datacr = None
         try:
-            datacr = self.fs.get_cr(line)
-            line = self.fs.ftpnorm(line)
-            path = self.fs.ftp2fs(line, datacr)
-            if not self.fs.lexists(path):
+            datacr = self.get_crdata2(line, mode='rfnr')
+            if not datacr[1]:
                 self.respond("550 No such file or directory.")
-            elif self.fs.realpath(path) == self.fs.realpath(self.fs.root):
-                self.respond("550 Can't rename the home directory.")
+            elif not datacr[1]:
+                self.respond("553 Can't rename the home directory.")
             else:
-                self.fs.rnfr = line
+                self.fs.rnfr = datacr[1]
                 self.respond("350 Ready for destination name.")
-        except:
-            self.respond("550 Can't find the file or directory.")
+        except FTPExceptionSent:
+            pass
         self.fs.close_cr(datacr)
 
     def ftp_RNTO(self, line):
@@ -2685,22 +2634,17 @@ class FTPHandler(asynchat.async_chat):
             return
         datacr = None
         try:
-            try:
-                datacr = self.fs.get_cr(line)
-                src = self.fs.ftp2fs(self.fs.rnfr, datacr)
-                line = self.fs.ftpnorm(line)
-                basedir, basename = os.path.split(line)
-                dst = self.fs.ftp2fs(basedir, datacr)
-                self.run_as_current_user(self.fs.rename, src, dst, basename)
-            except OSError, err:
-                why = _strerror(err)
-                self.log('FAIL RNFR/RNTO "%s ==> %s". %s.' \
-                         % (self.fs.ftpnorm(self.fs.rnfr), line, why))
-                self.respond('550 %s.' % why)
-            else:
-                self.log('OK RNFR/RNTO "%s ==> %s".' \
-                         % (self.fs.ftpnorm(self.fs.rnfr), line))
-                self.respond("250 Renaming ok.")
+            datacr = self.get_crdata2(line,'create')
+            oldname = self.fs.rnfr.path
+            if isinstance(oldname, (list, tuple)):
+                oldname = '/'.join(oldname)
+            self.try_as_current_user(self.fs.rename, (self.fs.rnfr, datacr), line=line)
+            self.fs.rnfr = None
+            self.log('OK RNFR/RNTO "%s ==> %s".' % \
+                    (_to_unicode(oldname), _to_unicode(line)))
+            self.respond("250 Renaming ok.")
+        except FTPExceptionSent:
+            pass
         finally:
             self.fs.rnfr = None
             self.fs.close_cr(datacr)
@@ -2718,12 +2662,12 @@ class FTPHandler(asynchat.async_chat):
             self.respond("200 Type set to: Binary.")
             self.current_type = 'i'
         else:
-            self.respond('504 Unsupported type "%s".' % line)
+            self.respond('504 Unsupported type "%s".' %line)
 
     def ftp_STRU(self, line):
         """Set file structure (obsolete)."""
         # obsolete (backward compatibility with older ftp clients)
-        if line in ('f', 'F'):
+        if line in ('f','F'):
             self.respond('200 File transfer structure set to: F.')
         else:
             self.respond('504 Unimplemented STRU type.')
@@ -2757,9 +2701,9 @@ class FTPHandler(asynchat.async_chat):
         # return STATus information about ftpd
         if not line:
             s = []
-            s.append('Connected to: %s:%s' % self.socket.getsockname()[:2])
+            s.append('Connected to: %s:%s' %self.socket.getsockname()[:2])
             if self.authenticated:
-                s.append('Logged in as: %s' % self.username)
+                s.append('Logged in as: %s' %self.username)
             else:
                 if not self.username:
                     s.append("Waiting for username.")
@@ -2769,38 +2713,38 @@ class FTPHandler(asynchat.async_chat):
                 type = 'ASCII'
             else:
                 type = 'Binary'
-            s.append("TYPE: %s; STRUcture: File; MODE: Stream" % type)
+            s.append("TYPE: %s; STRUcture: File; MODE: Stream" %type)
             if self.data_server:
                 s.append('Passive data channel waiting for connection.')
             elif self.data_channel:
                 bytes_sent = self.data_channel.tot_bytes_sent
                 bytes_recv = self.data_channel.tot_bytes_received
                 s.append('Data connection open:')
-                s.append('Total bytes sent: %s' % bytes_sent)
-                s.append('Total bytes received: %s' % bytes_recv)
+                s.append('Total bytes sent: %s' %bytes_sent)
+                s.append('Total bytes received: %s' %bytes_recv)
             else:
                 s.append('Data connection closed.')
 
             self.push('211-FTP server status:\r\n')
-            self.push(''.join([' %s\r\n' % item for item in s]))
+            self.push(''.join([' %s\r\n' %item for item in s]))
             self.respond('211 End of status.')
         # return directory LISTing over the command channel
         else:
             datacr = None
             try:
                 datacr = self.fs.get_cr(line)
-                iterator = self.run_as_current_user(self.fs.get_stat_dir, line, datacr)
-            except OSError, err:
-                self.respond('550 %s.' % _strerror(err))
+                iterator = self.try_as_current_user(self.fs.get_stat_dir, (line, datacr), line=line)
+            except FTPExceptionSent:
+                pass
             else:
-                self.push('213-Status of "%s":\r\n' % self.fs.ftpnorm(line))
+                self.push('213-Status of "%s":\r\n' %self.fs.ftpnorm(line))
                 self.push_with_producer(BufferedIteratorProducer(iterator))
                 self.respond('213 End of status.')
             self.fs.close_cr(datacr)
 
     def ftp_FEAT(self, line):
         """List all new features supported as defined in RFC-2398."""
-        features = ['EPRT', 'EPSV', 'MDTM', 'MLSD', 'REST STREAM', 'SIZE', 'TVFS']
+        features = ['EPRT','EPSV','MDTM','MLSD','REST STREAM','SIZE','TVFS']
         s = ''
         for fact in self.available_facts:
             if fact in self.current_facts:
@@ -2810,7 +2754,7 @@ class FTPHandler(asynchat.async_chat):
         features.append('MLST ' + s)
         features.sort()
         self.push("211-Features supported:\r\n")
-        self.push("".join([" %s\r\n" % x for x in features]))
+        self.push("".join([" %s\r\n" %x for x in features]))
         self.respond('211 End FEAT.')
 
     def ftp_OPTS(self, line):
@@ -2823,9 +2767,9 @@ class FTPHandler(asynchat.async_chat):
             else:
                 cmd, arg = line, ''
             # actually the only command able to accept options is MLST
-            assert (cmd.upper() == 'MLST'), 'Unsupported command "%s"' % cmd
+            assert (cmd.upper() == 'MLST'), 'Unsupported command "%s"' %cmd
         except AssertionError, err:
-            self.respond('501 %s.' % err)
+            self.respond('501 %s.' %err)
         else:
             facts = [x.lower() for x in arg.split(';')]
             self.current_facts = [x for x in facts if x in self.available_facts]
@@ -2854,7 +2798,7 @@ class FTPHandler(asynchat.async_chat):
         """Return help text to the client."""
         if line:
             if line.upper() in proto_cmds:
-                self.respond("214 %s" % proto_cmds[line.upper()])
+                self.respond("214 %s" %proto_cmds[line.upper()])
             else:
                 self.respond("501 Unrecognized command.")
         else:
@@ -2865,7 +2809,7 @@ class FTPHandler(asynchat.async_chat):
                 keys.sort()
                 while keys:
                     elems = tuple((keys[0:8]))
-                    cmds.append(' %-6s' * len(elems) % elems + '\r\n')
+                    cmds.append(' %-6s' * len(elems) %elems + '\r\n')
                     del keys[0:8]
                 return ''.join(cmds)
 
@@ -2981,7 +2925,7 @@ class FTPServer(asyncore.dispatcher):
         asyncore.loop() function: timeout, use_poll, map and count.
         """
         if not 'count' in kwargs:
-            log("Serving FTP on %s:%s" % self.socket.getsockname()[:2])
+            log("Serving FTP on %s:%s" %self.socket.getsockname()[:2])
 
         # backward compatibility for python < 2.4
         if not hasattr(self, '_map'):
@@ -3010,7 +2954,7 @@ class FTPServer(asyncore.dispatcher):
     def handle_accept(self):
         """Called when remote client initiates a connection."""
         sock_obj, addr = self.accept()
-        log("[]%s:%s Connected." % addr[:2])
+        log("[]%s:%s Connected." %addr[:2])
 
         handler = self.handler(sock_obj, self)
         ip = addr[0]