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 = {
}
-# 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).
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
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.",
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:
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
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
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
"""
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.
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()
# 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):
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()
# 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
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:
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):
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
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
# 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.
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
"""
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
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
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()
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
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
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):
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):
'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
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
# --- 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.
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
# 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
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
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
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):
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)
# 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)
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:
# - 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
"""
# 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.')
"""
# 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:
# 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:
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)
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:
# 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:
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
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:
# 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)
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):
# 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):
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:
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
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':
else:
self.respond("530 Authentication failed.")
self.log('Authentication failed: unknown username "%s".'
- % self.username)
+ %self.username)
self.username = ""
def ftp_REIN(self, line):
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."""
"""
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):
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)
"""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):
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):
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)
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.')
# 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.")
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:
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):
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]
"""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:
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)
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'):
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]