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',
}
-# 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).
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.")
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
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:
# --- 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.
"""
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]
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
def __check_path(self, cmd, line):
"""Check whether a path is valid."""
-
+
# Always true, we will only check later, once we have a cursor
return True
self.__out_dtp_queue = (data, isproducer, file)
def log(self, msg):
- """Log a message, including additional identifying session data."""
+ """Log a message, including additional identifying session data."""
log("[%s]@%s:%s %s" %(self.username, self.remote_ip,
self.remote_port, msg))
# --- 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.
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)
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)
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],]
-
+
listing = []
for nl in nodelist:
if isinstance(nl.path, (list, tuple)):
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)
data = ''
if listing:
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
# if no argument, fall back on cwd as default
if not line:
line = ''
-
+
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)
"""
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:
datacr = None
try:
- datacr = self.fs.get_crdata(line,mode='create')
- 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, datacr, datacr[2], 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:
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:
# 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."""
"""
datacr = None
try:
- datacr = self.fs.get_crdata(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 = _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)
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)
+
+ 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.")
"""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 OSError, err:
- why = _strerror(err)
- self.log('FAIL DELE "%s". %s.' %(line, why))
- 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):
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):
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)
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))