summaryrefslogtreecommitdiff
path: root/addons/document_ftp/ftpserver/ftpserver.py
diff options
context:
space:
mode:
authorP. Christeas <p_christ@hol.gr>2010-07-14 15:52:28 +0300
committerP. Christeas <p_christ@hol.gr>2010-07-14 15:52:28 +0300
commit5a5608d71b2b4188adbd0d4d004f2a280d8e3388 (patch)
tree7f7232730702a42eb8d91390a16b90282e10df7b /addons/document_ftp/ftpserver/ftpserver.py
parent26935b22e6e045097f3c67418c982392e9fa3a9e (diff)
Document FTP: restructure the function calling, more consistent exceptions.
Hopefully, FTP now will behave better to clients like Firefox. It is better to pass the node-API messages to the FTP layer, than always return 550. Conflicts: document_ftp/ftpserver/ftpserver.py bzr revid: p_christ@hol.gr-20100714125228-7xs3zbi72may4198
Diffstat (limited to 'addons/document_ftp/ftpserver/ftpserver.py')
-rw-r--r--addons/document_ftp/ftpserver/ftpserver.py307
1 files changed, 156 insertions, 151 deletions
diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py
index 528ce057ff9..88e48bf31bf 100644
--- a/addons/document_ftp/ftpserver/ftpserver.py
+++ b/addons/document_ftp/ftpserver/ftpserver.py
@@ -1354,6 +1354,11 @@ class AbstractedFS:
# --- 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.
@@ -1453,6 +1458,13 @@ class FTPHandler(asynchat.async_chat):
self._epsvall = False
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,
+ }
# mlsx facts attributes
self.current_facts = ['type', 'perm', 'size', 'modify']
@@ -1815,6 +1827,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, e:
+ 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.
@@ -2037,18 +2101,12 @@ class FTPHandler(asynchat.async_chat):
line = ''
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='list')
- iterator = self.run_as_current_user(self.fs.get_list_dir, datacr)
- except IOError, err:
+ 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)
- self.respond('550 %s.'% err.strerror)
- return
- except OSError, err:
- self.fs.close_cr(datacr)
- why = _strerror(err)
- self.log('FAIL LIST "%s". %s.' %(line, why))
- self.respond('550 %s.' %why)
return
+
try:
self.log('OK LIST "%s". Transfer starting.' % line)
producer = BufferedIteratorProducer(iterator)
@@ -2066,11 +2124,11 @@ class FTPHandler(asynchat.async_chat):
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='list')
+ datacr = self.get_crdata2(line, mode='list')
if not datacr:
datacr = ( None, None, None )
if self.fs.isdir(datacr[1]):
- nodelist = self.run_as_current_user(self.fs.listdir, datacr)
+ nodelist = self.try_as_current_user(self.fs.listdir, (datacr,))
else:
# if path is a file we just list its name
nodelist = [datacr[1],]
@@ -2081,15 +2139,8 @@ class FTPHandler(asynchat.async_chat):
listing.append(nl.path[-1])
else:
listing.append(nl.path) # assume string
- except IOError, err:
- self.fs.close_cr(datacr)
- self.respond('550 %s.'% err.strerror)
- return
- except OSError, err:
+ except FTPExceptionSent:
self.fs.close_cr(datacr)
- why = _strerror(err)
- self.log('FAIL NLST "%s". %s.' %(line, why))
- self.respond('550 %s.' %why)
return
self.fs.close_cr(datacr)
@@ -2116,16 +2167,14 @@ class FTPHandler(asynchat.async_chat):
line = ''
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='list')
+ datacr = self.get_crdata2(line, mode='list')
perms = self.authorizer.get_perms(self.username)
- iterator = self.run_as_current_user(self.fs.format_mlsx, datacr[0], datacr[1].parent,
- [datacr[1],], 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 EnvironmentError, 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
@@ -2147,19 +2196,17 @@ class FTPHandler(asynchat.async_chat):
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='list')
+ datacr = self.get_crdata2(line, mode='list')
# RFC-3659 requires 501 response code if path is not a directory
if not self.fs.isdir(datacr[1]):
err = 'No such directory'
self.log('FAIL MLSD "%s". %s.' %(line, err))
self.respond("501 %s." %err)
return
- listing = self.run_as_current_user(self.fs.listdir, datacr)
- 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)
@@ -2175,19 +2222,10 @@ class FTPHandler(asynchat.async_chat):
"""
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='file')
- fd = self.run_as_current_user(self.fs.open, datacr, 'rb')
- except OSError, 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
- except IOError, err:
- self.fs.close_cr(datacr)
- why = err.strerror
- self.log('FAIL RETR "%s". %s.' %(line, why))
- self.respond('550 %s.' %why)
return
if self.restart_position:
@@ -2230,23 +2268,12 @@ class FTPHandler(asynchat.async_chat):
datacr = None
try:
- datacr = self.fs.get_crdata(line,mode='create')
- except OSError, 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:
- mode = 'r+'
- try:
- fd = self.run_as_current_user(self.fs.create, datacr, datacr[2], mode + 'b')
- except IOError, 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'))
+ 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:
@@ -2301,16 +2328,19 @@ class FTPHandler(asynchat.async_chat):
if line:
- datacr = self.fs.get_crdata(line, mode='create')
- # TODO
+ datacr = self.get_crdata2(line, mode='create')
+ # TODO
else:
- # TODO
+ # 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:
@@ -2513,22 +2543,15 @@ class FTPHandler(asynchat.async_chat):
# Search for official references about this behaviour.
datacr = None
try:
- datacr = self.fs.get_crdata(line,'cwd')
- self.run_as_current_user(self.fs.chdir, 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 EnvironmentError, err:
- self.log("Could not cwd: %s" % 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)
- self.fs.close_cr(datacr)
+ except FTPExceptionSent:
+ return
+ finally:
+ self.fs.close_cr(datacr)
def ftp_CDUP(self, line):
"""Change into the parent directory."""
@@ -2556,21 +2579,17 @@ class FTPHandler(asynchat.async_chat):
"""
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='file')
+ datacr = self.get_crdata2(line, mode='file')
#if self.fs.isdir(datacr[1]):
# 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, datacr)
- except EnvironmentError, err:
- why = err.strerror
- self.log('FAIL SIZE "%s". %s.' %(line, why))
- if err.errno == errno.ENOENT:
- self.respond("404 %s." % why)
- else:
- self.respond('550 %s.' % why)
+ 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)
@@ -2581,36 +2600,35 @@ class FTPHandler(asynchat.async_chat):
3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659.
"""
datacr = None
+
try:
- datacr = self.fs.get_crdata(line)
- if not self.fs.isfile(datacr):
- 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, datacr)
+ 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)
+ #if not self.fs.isfile(datacr[1]):
+ # raise IOError(errno.EPERM, "%s is not a regular file" % 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)
- except OSError, err:
- why = _strerror(err)
- self.log('FAIL MDTM "%s". %s.' %(line, why))
- self.respond('550 %s.' %why)
- self.fs.close_cr(datacr)
+ except FTPExceptionSent:
+ return
+ finally:
+ self.fs.close_cr(datacr)
def ftp_MKD(self, line):
"""Create the specified directory."""
try:
- datacr = self.fs.get_crdata(line, mode='create')
- self.run_as_current_user(self.fs.mkdir, datacr, datacr[2])
- except IOError, err:
- self.log('FAIL MKD "%s". %s.' %(line, err.strerror))
- self.respond('550 %s.' % err.strerror)
- 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.respond("257 Directory created.")
@@ -2620,39 +2638,30 @@ class FTPHandler(asynchat.async_chat):
"""Remove the specified directory."""
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='delete')
+ datacr = self.get_crdata2(line, mode='delete')
if not datacr[1]:
msg = "Can't remove root directory."
- self.respond("550 %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, datacr)
- except OSError, err:
- why = _strerror(err)
- self.log('FAIL RMD "%s". %s.' %(line, why))
- self.respond('550 %s.' %why)
- else:
+ 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_crdata(line, mode='delete')
- self.run_as_current_user(self.fs.remove, datacr)
- except EnvironmentError, err:
- why = err.strerror
- self.log('FAIL DELE "%s". %s.' %(line, why))
- if err.errno == errno.ENOENT:
- self.respond('404 %s.' % why)
- else:
- self.respond('550 %s.' %why)
- else:
+ 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):
@@ -2660,16 +2669,16 @@ class FTPHandler(asynchat.async_chat):
here, see RNTO command)"""
datacr = None
try:
- datacr = self.fs.get_crdata(line, mode='rfnr')
+ datacr = self.get_crdata2(line, mode='rfnr')
if not datacr[1]:
self.respond("550 No such file or directory.")
elif not datacr[1]:
- self.respond("550 Can't rename the home directory.")
+ self.respond("553 Can't rename the home directory.")
else:
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):
@@ -2681,21 +2690,17 @@ class FTPHandler(asynchat.async_chat):
return
datacr = None
try:
- try:
- datacr = self.fs.get_crdata(line,'create')
- oldname = self.fs.rnfr.path
- if isinstance(oldname, (list, tuple)):
- oldname = '/'.join(oldname)
- self.run_as_current_user(self.fs.rename, self.fs.rnfr, datacr)
- self.fs.rnfr = None
- self.log('OK RNFR/RNTO "%s ==> %s".' % \
- (_to_unicode(oldname), _to_unicode(line)))
- self.respond("250 Renaming ok.")
- except EnvironmentError, err:
- why = _strerror(err)
- self.log('FAIL RNFR/RNTO "%s ==> %s". %s.' \
- % (_to_unicode(oldname), _to_unicode(line), why))
- self.respond('550 %s.' %why)
+ 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)
@@ -2784,9 +2789,9 @@ class FTPHandler(asynchat.async_chat):
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_with_producer(BufferedIteratorProducer(iterator))