diff options
| author | P. Christeas <p_christ@hol.gr> | 2010-07-14 15:52:28 +0300 |
|---|---|---|
| committer | P. Christeas <p_christ@hol.gr> | 2010-07-14 15:52:28 +0300 |
| commit | 5a5608d71b2b4188adbd0d4d004f2a280d8e3388 (patch) | |
| tree | 7f7232730702a42eb8d91390a16b90282e10df7b /addons/document_ftp/ftpserver/ftpserver.py | |
| parent | 26935b22e6e045097f3c67418c982392e9fa3a9e (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.py | 307 |
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)) |
