summaryrefslogtreecommitdiff
path: root/chromium/third_party/pyftpdlib
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/third_party/pyftpdlib
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/third_party/pyftpdlib')
-rw-r--r--chromium/third_party/pyftpdlib/OWNERS2
-rw-r--r--chromium/third_party/pyftpdlib/README.chromium15
-rw-r--r--chromium/third_party/pyftpdlib/src/CREDITS149
-rw-r--r--chromium/third_party/pyftpdlib/src/HISTORY649
-rw-r--r--chromium/third_party/pyftpdlib/src/INSTALL4
-rw-r--r--chromium/third_party/pyftpdlib/src/LICENSE22
-rw-r--r--chromium/third_party/pyftpdlib/src/README12
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/anti_flood_ftpd.py108
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/basic_ftpd.py75
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/keycert.pem32
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/md5_ftpd.py66
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/throttled_ftpd.py60
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/tls_ftpd.py60
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/unix_daemon.py205
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/unix_ftpd.py54
-rw-r--r--chromium/third_party/pyftpdlib/src/demo/winnt_ftpd.py57
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/adoptions.lnk.html4
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/faq.html167
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/index.html100
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/install.html51
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/logo.pngbin0 -> 13008 bytes
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/release-notes.html63
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/rfcs-compliance.html84
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/roadmap.lnk.html5
-rw-r--r--chromium/third_party/pyftpdlib/src/doc/tutorial.html427
-rw-r--r--chromium/third_party/pyftpdlib/src/pyftpdlib/__init__.py0
-rw-r--r--chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/__init__.py0
-rw-r--r--chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/authorizers.py620
-rw-r--r--chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/filesystems.py60
-rw-r--r--chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/handlers.py448
-rw-r--r--chromium/third_party/pyftpdlib/src/pyftpdlib/ftpserver.py3966
-rw-r--r--chromium/third_party/pyftpdlib/src/setup.py96
32 files changed, 7661 insertions, 0 deletions
diff --git a/chromium/third_party/pyftpdlib/OWNERS b/chromium/third_party/pyftpdlib/OWNERS
new file mode 100644
index 00000000000..b7565e764ca
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/OWNERS
@@ -0,0 +1,2 @@
+phajdan.jr@chromium.org
+wtc@chromium.org
diff --git a/chromium/third_party/pyftpdlib/README.chromium b/chromium/third_party/pyftpdlib/README.chromium
new file mode 100644
index 00000000000..7780c6ade62
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/README.chromium
@@ -0,0 +1,15 @@
+Name: Python FTP server library
+Short Name: pyftpdlib
+URL: http://code.google.com/p/pyftpdlib/
+License: MIT
+License File: src/LICENSE
+Version: unknown
+Security Critical: no
+
+Description:
+Python FTP server library provides an high-level portable interface to easily
+write asynchronous FTP servers with Python. Based on asyncore framework
+pyftpdlib is currently the most complete RFC-959 FTP server implementation
+available for Python programming language.
+
+Learn more by visiting: http://code.google.com/p/pyftpdlib/wiki/FAQ
diff --git a/chromium/third_party/pyftpdlib/src/CREDITS b/chromium/third_party/pyftpdlib/src/CREDITS
new file mode 100644
index 00000000000..12ee43b55e2
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/CREDITS
@@ -0,0 +1,149 @@
+
+Intro
+=====
+
+I would like to recognize some of the people who have been
+instrumental in the development of pyftpdlib.
+I'm sure I am forgetting some people (feel free to email
+me), but here is a short list.
+It's modeled after the Linux CREDITS file where the fields
+are:
+name (N), e-mail (E), web-address (W), country (C) description (D).
+Really thanks to all of you.
+
+
+Maintainers
+===========
+
+N: Giampaolo Rodola'
+C: Italy
+E: g.rodola@gmail.com
+D: Original pyftpdlib author and maintainer
+
+N: Jay Loden
+C: NJ, USA
+E: jloden@gmail.com
+W: http://www.jayloden.com
+D: OS X and Linux platform development/testing
+
+N: Silas Sewell
+C: Denver, USA
+E: silas@sewell.ch
+W: http://www.silassewell.com
+D: Fedora port maintainer
+
+N: Li-Wen Hsu
+C: Taiwan
+E: lwhsu@lwhsu.org
+W: http://lwhsu.org
+D: FreeBSD port maintainer
+
+
+Contributors
+============
+
+N: Anatoly Techtonik
+C: Belarus
+E: techtonik@gmail.com
+D: Inclusion of pyftpdlib in Far Manager, a file and archive manager for Windows
+ http://www.farmanager.com/enforum/viewtopic.php?t=640&highlight=&sid=12d4d90f27f421243bcf7a0e3c516efb.
+
+N: Arkadiusz Wahlig
+C: Germany
+W: http://arkadiusz-wahlig.blogspot.com
+D: Inclusion of pyftpdlib in gpftpd project, an FTP daemon for managing files
+ hosted on Google Pages
+ (http://arkadiusz-wahlig.blogspot.com/2008/04/hosting-files-on-google.html).
+
+N: Walco van Loon
+C: Netherlands
+E: walco@n--tree.net
+D: Inclusion of pyftpdlib in aksy project
+ (http://walco.n--tree.net/projects/aksy).
+
+N: Stephane Travostino
+E: stephane.travostino@combo.cc
+D: Inclusion of pyftpdlib in Shareme project
+ (http://bbs.archlinux.org/viewtopic.php?pid=431474).
+
+N: Shinya Okano
+C: Japan
+E: xxshss@yahoo.co.jp
+D: Japanese translation of pyftpdlib announces. Inclusion of pyftpdlib in
+ unbox-ftpd project (http://code.google.com/p/unboxftpd).
+
+N: Yan Raber
+C: Italy
+E: yanraber@gmail.com
+D: Fix of Issue #9 (Path traversal vulnerability)
+
+N: Alex Martelli
+C: Italy
+E: aleax@gmail.com
+D: Various useful suggestions
+
+N: Knic
+C: Redmond, USA
+E: oneeyedelf1@googlemail.com
+D: Bug report #24 (some troubles on PythonCE), tester for various platforms
+ including Windows Mobile, Windows Server 2008 and various 64 bit OSes.
+
+N: Greg Copeland
+E: gcopeland@efjohnson.com
+D: Bug report #16 (Extending compatibility with older python versions)
+
+N: Roger Erens
+E: rogererens@gmail.com
+D: Bug report affecting unix_ftpd.py's authorizer
+
+N: Coronado Ivan
+D: Bug report #70 (Wrong NOOP response code)
+
+N: Rauli Ruohonen
+D: Bug report #71 (Socket handles are leaked when a data transfer is in progress
+ and user QUITs)
+
+N: Equand
+E: equand@gmail.com
+D: Bug report #77 (incorrect OOB data management on FreeBSD).
+
+N: fogwraith
+E: fogwraith@gmail.com
+D: Bug report #80 (demo/md5_ftpd.py should use hashlib module instead of the
+ deprecated md5 module)
+
+N: Bram Neijt
+E: bneijt@gmail.com
+D: Bug report #100, author of ShareFTP project:
+ http://git.logfish.net/shareftp.git/
+
+N: Michele Petrazzo
+C: Italy
+E: michele.petrazzo@gmail.com
+D: Creation of the demo/unix_daemon.py code.
+
+N: Wentao Han
+D: Bug report #104 (socket.accept() might return None instead of a valid address
+ and EPIPE might be thrown by asyncore on OS X).
+
+N: Ben Timby
+E: btimby@gmail.com
+C: USA
+D: (issue #127) multiple masqueraded IP addresses patch.
+
+N: Bernd Deichmann
+W: http://deichmann-edv.de/
+D: issue 156
+
+N: Andrew Scheller
+E: gcode@loowis.durge.org
+C: UK
+D: issue 158, 161, 163, 167, 175
+
+N: guppyism
+E: guppyism@gmail.com
+D: issue 187
+
+N: Darren Worrall
+E: darren.worrall@gmail.com
+D: issue 198
diff --git a/chromium/third_party/pyftpdlib/src/HISTORY b/chromium/third_party/pyftpdlib/src/HISTORY
new file mode 100644
index 00000000000..dcef1a36abc
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/HISTORY
@@ -0,0 +1,649 @@
+Bug tracker at http://code.google.com/p/pyftpdlib/issues/list
+
+
+History
+=======
+
+Version: 0.7.0 - Date: XXXX-XX-XX
+---------------------------------
+
+Enhancements:
+
+ * Issue #152: uploads (from server to client) on UNIX are now from 2x (Linux)
+ to 3x (OSX) faster because of sendfile(2) system call usage.
+
+ * Issue #155: AbstractedFS "root" and "cwd" are no longer read-only properties
+ but can be set via setattr().
+
+ * Issue #168: added FTPHandler.logerror() method. It can be overridden to
+ provide more information (e.g. username) when logging exception tracebacks.
+
+ * Issue #174: added support for SITE CHMOD command (change file mode).
+
+ * Issue #177: setuptools is now used in setup.py
+
+ * Issue #178: added anti flood script in demo directory.
+
+ * Issue #181: added CallEvery class to call a function every x seconds.
+
+ * Issue #185: pass Debian licenscheck tool.
+
+ * Issue #189: the internal scheduler has been rewritten from scratch and it is
+ an order of magnitude faster, especially for operations like cancel() which
+ are involved when clients are disconnected (hence invoked very often).
+ Some benchmarks:
+ schedule : +0.5x
+ reschedule : +1.7x
+ cancel : +477x (with 1 milion scheduled functions)
+ run: : +8x
+ Also, a single scheduled function now consumes 1/3 of the memory thanks
+ to __slots__ usage.
+
+ * Issue #196: added callback for failed login attempt.
+
+ * Issue #200: FTPServer.server_forever() is now a class method.
+
+Bugfixes:
+
+ * Issue #156: data connection must be closed before sending 226/426 reply.
+ This was against RFC-959 and was causing problems with older FTP clients.
+
+ * Issue #161: MLSD 'unique' fact can provide the same value for files having a
+ similar device/inode but that in fact are different.
+ (patch by Andrew Scheller)
+
+ * Issue #162: (FTPS) SSL shutdown() is not invoked for the control connection.
+
+ * Issue #163: FEAT erroneously reports MLSD. (patch by Andrew Scheller)
+
+ * Issue #166: (FTPS) an exception on send() can cause server to crash (DoS).
+
+ * Issue #167: fix some typos returned on HELP.
+
+ * Issue #170: PBSZ and PROT commands are now allowed before authentication
+ fixing problems with non-compliant FTPS clients.
+
+ * Issue #171: (FTPS) an exception when shutting down the SSL layer can cause
+ server to crash (DoS).
+
+ * Issue #173: file last modification time shown in LIST response might be in a
+ language different than English causing problems with some clients.
+
+ * Issue #175: FEAT response now omits to show those commands which are removed
+ from proto_cmds map.
+
+ * Issue #176: SO_REUSEADDR option is now used for passive data sockets to
+ prevent server running out of free ports when using passive_ports directive.
+
+ * Issue #187: match proftpd LIST format for files having last modification time
+ > 6 months.
+
+ * Issue #188: fix maximum recursion depth exceeded exception occurring if
+ client quickly connects and disconnects data channel.
+
+ * Issue #191: (FTPS) during SSL shutdown() operation the server can end up in
+ an infinite loop hogging CPU resources.
+
+ * Issue #199: UnixAuthorizer with require_valid_shell option is broken.
+
+ * Issue #202: added benchmark script.
+
+Major API changes since 0.6.0:
+
+ * New FTPHandler.use_sendfile attribute.
+ * sendfile() is now automatically used instead of plain send() if
+ pysendfile module is installed.
+ * FTPServer.serve_forever() is a classmethod.
+ * AbstractedFS root and cwd properties can now be set via setattr().
+ * New CallLater class.
+ * New FTPHandler.on_login_failed(username, password) method.
+ * New FTPHandler.logerror(msg) method.
+ * New FTPHandler.log_exception(instance) method.
+
+
+Version: 0.6.0 - Date: 2011-01-24
+---------------------------------
+
+Enhancements:
+
+ * Issue #68: added full FTPS (FTP over SSL/TLS) support provided by new
+ TLS_FTPHandler class defined in pyftpdlib.contrib.handlers module.
+
+ * Issue #86: pyftpdlib now reports all ls and MDTM timestamps as GMT times,
+ as recommended in RFC-3659. A FTPHandler.use_gmt_times attributed has been
+ added and can be set to False in case local times are desired instead.
+
+ * Issue #124: pyftpdlib now accepts command line options to configure a stand
+ alone anonymous FTP server when running pyftpdlib with python's -m option.
+
+ * Issue #125: logs are now provided in a standardized format parsable by log
+ analyzers. FTPHandler class provides two new methods to standardize both
+ commands and transfers logging: log_cmd() and log_transfer().
+
+ * Issue #127: added FTPHandler.masquerade_address_map option which allows you
+ to define multiple 1 to 1 mappings in case you run a FTP server with
+ multiple private IP addresses behind a NAT firewall with multiple public
+ IP addresses.
+
+ * Issue #128: files and directories owner and group names and os.readlink are
+ now resolved via AbstractedFS methods instead of in format_list().
+
+ * Issue #129 and #139: added 4 new callbacks to FTPHandler class:
+ on_incomplete_file_sent(), on_incomplete_file_received(), on_login() and
+ on_logout().
+
+ * Issue #130: added UnixAuthorizer and WindowsAuthorizer classes defined in the
+ new pyftpdlib.contrib.authorizers module.
+
+ * Issue #131: pyftpdlib is now able to serve both IPv4 and IPv6 at the same
+ time by using a single socket.
+
+ * Issue #133: AbstractedFS constructor now accepts two argumets: root and
+ cmd_channel breaking compatibility with previous version. Also, root and and
+ cwd attributes became properties. The previous bug consisting in re-setting
+ the root from the ftp handler after user login has been fixed to ease the
+ development of subclasses.
+
+ * Issue #134: enabled TCP_NODELAY socket option for the FTP command channels
+ resulting in pyftpdlib being twice faster.
+
+ * Issue #135: Python 2.3 support has been removed.
+
+ * Issue #137: added new pyftpdlib.contrib.filesystems module within
+ UnixFilesystem class which permits the client to escape its home directory
+ and navigate the real filesystem.
+
+ * Issue #138: added DTPHandler.get_elapsed_time() method which returns the
+ transfer elapsed time in seconds.
+
+ * Issue #144: a "username" parameter is now passed to authorizer's
+ terminate_impersonation() method.
+
+ * Issue #149: ftpserver.proto_cmds dictionary refactoring and get rid of
+ _CommandProperty class.
+
+Bugfixes:
+
+ * Issue #120: an ActiveDTP() instance is not garbage collected in case a
+ client issuing PORT disconnects before establishing the data connection.
+
+ * Issue #122: a wrong variable name was used in AbstractedFS.validpath method.
+
+ * Issue #123: PORT command doesn't bind to correct address in case an alias
+ is created for the local network interface.
+
+ * Issue #140: pathnames returned in PWD response should have double-quotes '"'
+ escaped.
+
+ * Issue #143: EINVAL not properly handled causes server crash on OSX.
+
+ * Issue #146: SIZE and MDTM commands are now rejected unless the "l" permission
+ has been specified for the user.
+
+ * Issue #150: path traversal bug: it is possible to move/rename a file outside
+ of the user home directory.
+
+Major API changes since 0.5.2
+
+ * removed support for Python 2.3.
+
+ * all classes are now new-style classes.
+
+ * AbstractedFS class:
+ * __init__ now accepts two arguments: root and cmd_channel.
+ * root and cwd attributes are now read-only properties.
+ * 3 new methods have been added:
+ - get_user_by_uid()
+ - get_group_by_gid()
+ - readlink()
+
+ * FTPHandler class:
+ * new class attributes:
+ - use_gmt_times
+ - tcp_no_delay
+ - masquerade_address_map
+ * new methods:
+ - on_incomplete_file_sent()
+ - on_incomplete_file_received()
+ - on_login()
+ - on_logout()
+ - log_cmd()
+ - log_transfer()
+ * proto_cmds class attribute has been added. The FTPHandler class no longer
+ relies on "ftpserver.proto_cmds" global dictionary but on
+ "ftpserver.FTPHandler.proto_cmds" instead.
+
+ * FTPServer class:
+ - max_cons attribute defaults to 512 by default instead of 0 (unlimited).
+ - server_forever()'s map argument is gone.
+
+ * DummyAuthorizer:
+ - ValueError exceptions are now raised instead of AuthorizerError.
+ - terminate_impersonation() method now expects a "username" parameter.
+
+ * DTPHandler.get_elapsed_time() method has been added.
+
+ * Added a new package in pyftpdlib namespace: "contrib". Modules (and classes)
+ defined here:
+ - pyftpdlib.contrib.handlers.py (TLS_FTPHandler)
+ - pyftpdlib.contrib.authorizers.py (UnixAuthorizer, WindowsAuthorizer)
+ - pyftpdlib.contrib.filesystems (UnixFilesystem)
+
+Minor API changes since 0.5.2
+
+ * FTPHandler renamed objects:
+ data_server -> _dtp_acceptor
+ current_type -> _current_type
+ restart_position -> _restart_position
+ quit_pending -> _quit_pending
+ af -> _af
+ on_dtp_connection -> _on_dtp_connection
+ on_dtp_close -> _on_dtp_close
+ idler -> _idler
+
+ * AbstractedFS.rnfr attribute moved to FTPHandler._rnfr.
+
+
+Version: 0.5.2 - Date: 2009-09-14
+---------------------------------
+
+Enhancements:
+
+ * Issue #103: added unix_daemon.py script.
+
+ * Issue #108: a new ThrottledDTPHandler class has been added for limiting the
+ speed of downloads and uploads.
+
+Bugfixes:
+
+ * Issue #100: fixed a race condition in FTPHandler constructor which could
+ throw an exception in case of connection bashing (DoS). (thanks Bram Neijt)
+
+ * Issue #102: FTPServer.close_all() now removes any unfired delayed call left
+ behind to prevent potential memory leaks.
+
+ * Issue #104: fixed a bug in FTPServer.handle_accept() where socket.accept()
+ could return None instead of a valid address causing the server to crash.
+ (OS X only, reported by Wentao Han)
+
+ * Issue #104: an unhandled EPIPE exception might be thrown by asyncore.recv()
+ when dealing with ill-behaved clients on OS X . (reported by Wentao Han)
+
+ * Issue #105: ECONNABORTED might be thrown by socket.accept() on FreeBSD
+ causing the server to crash.
+
+ * Issue #109: an unhandled EBADF exception might be thrown when using poll() on
+ OS X and FreeBSD.
+
+ * Issue #111: the license used was not MIT as stated in source files.
+
+ * Issue #112: fixed a MDTM related test case failure occurring on 64 bit OSes.
+
+ * Issue #113: fixed unix_ftp.py which was treating anonymous as a normal user.
+
+ * Issue #114: MLST is now denied unless the "l" permission has been specified
+ for the user.
+
+ * Issue #115: asyncore.dispatcher.close() is now called before doing any other
+ cleanup operation when client disconnects. This way we avoid an endless loop
+ which hangs the server in case an exception is raised in close() method.
+ (thanks Arkadiusz Wahlig)
+
+ * Issue #116: extra carriage returns were added to files transferred in ASCII
+ mode.
+
+ * Issue #118: CDUP always changes to "/".
+
+ * Issue #119: QUIT sent during a transfer caused a memory leak.
+
+
+API changes since 0.5.1:
+
+ * ThrottledDTPHandler class has been added.
+
+ * FTPHandler.process_command() method has been added.
+
+
+Version: 0.5.1 - Date: 2009-01-21
+---------------------------------
+
+Enhancements:
+
+ * Issue #79: added two new callback methods to FTPHandler class to handle
+ "on_file_sent" and "on_file_received" events.
+
+ * Issue #82: added table of contents in documentation.
+
+ * Issue #92: ASCII transfers are now 200% faster on those systems using
+ "\r\n" as line separator (typically Windows).
+
+ * Issue #94: a bigger buffer size for send() and recv() has been set resulting
+ in a considerable speedup (about 40% faster) for both incoming and outgoing
+ data transfers.
+
+ * Issue #98: added preliminary support for SITE command.
+
+ * Issue #99: a new script implementing FTPS (FTP over TLS/SSL) has been added
+ to the demo directory. See:
+ http://code.google.com/p/pyftpdlib/source/browse/trunk/demo/tls_ftpd.py
+
+Bugfixes:
+
+ * Issue #78: the idle timeout of passive data connections gets stopped in case
+ of rejected "site-to-site" connections.
+
+ * Issue #80: demo/md5_ftpd.py should use hashlib module instead of the
+ deprecated md5 module.
+
+ * Issue #81: fixed some tests which were failing on SunOS.
+
+ * Issue #84: fixed a very rare unhandled exception which could occur when
+ retrieving the first bytes of a corrupted file.
+
+ * Issue #85: a positive MKD response is supposed to include the name of the
+ new directory.
+
+ * Issue #87: SIZE should be rejected when the current TYPE is ASCII.
+
+ * Issue #88: REST should be rejected when the current TYPE is ASCII.
+
+ * Issue #89: "TYPE AN" was erroneously treated as synonym for "TYPE A" when
+ "TYPE L7" should have been used instead.
+
+ * Issue #90: an unhandled exception can occur when using MDTM against a file
+ modified before year 1900.
+
+ * Issue #91: an unhandled exception can occur in case accept() returns None
+ instead of a socket (it happens sometimes).
+
+ * Issue #95: anonymous is now treated as any other case-sensitive user.
+
+API changes since 0.5.0:
+
+ * FTPHandler gained a new "_extra_feats" private attribute.
+
+ * FTPHandler gained two new methods: "on_file_sent" and "on_file_received".
+
+
+Version: 0.5.0 - Date: 2008-09-20
+---------------------------------
+
+Enhancements:
+
+ * Issue #72: pyftpdlib now provides configurable idle timeouts to disconnect
+ client after a long time of inactivity.
+
+ * Issue #73: imposed a delay before replying for invalid credentials to
+ minimize the risk of brute force password guessing (RFC-1123).
+
+ * Issue #74: it is now possible to define permission exceptions for certain
+ directories (e.g. creating a user which does not have write permission except
+ for one sub-directory in FTP root).
+
+ * Improved bandwidth throttling capabilities of demo/throttled_ftpd.py script
+ by having used the new CallLater class which drastically reduces the number
+ of time.time() calls.
+
+Bugfixes:
+
+ * Issue #62: some unit tests were failing on certain dual core machines.
+
+ * Issue #71: socket handles are leaked when a data transfer is in progress and
+ user QUITs.
+
+ * Issue #75: orphaned file was left behind in case STOU failed for insufficient
+ user permissions.
+
+ * Issue #77: incorrect OOB data management on FreeBSD.
+
+API changes since 0.4.0:
+
+ * FTPHandler, DTPHandler, PassiveDTP and ActiveDTP classes gained a new timeout
+ class attribute.
+
+ * DummyAuthorizer class gained a new override_perm method.
+
+ * A new class called CallLater has been added.
+
+ * AbstractedFS.get_stat_dir method has been removed.
+
+
+Version: 0.4.0 - Date: 2008-05-16
+---------------------------------
+
+Enhancements:
+
+ * Issue #65: It is now possible to assume the id of real users when using
+ system dependent authorizers.
+
+ * Issue #67: added IPv6 support.
+
+Bugfixes:
+
+ * Issue #64: Issue #when authenticating as anonymous user when using UNIX and
+ Windows authorizers.
+
+ * Issue #66: WinNTAuthorizer does not determine the real user home directory.
+
+ * Issue #69: DummyAuthorizer incorrectly uses class attribute instead of
+ instance attribute for user_table dictionary.
+
+ * Issue #70: a wrong NOOP response code was given.
+
+API changes since 0.3.0:
+
+ * DummyAuthorizer class has now two new methods: impersonate_user() and
+ terminate_impersonation().
+
+
+Version: 0.3.0 - Date: 2008-01-17
+---------------------------------
+
+Enhancements:
+
+ * Issue #42: implemented FEAT command (RFC-2389).
+
+ * Issue #48: real permissions, owner, and group for files on UNIX platforms are
+ now provided when processing LIST command.
+
+ * Issue #51: added the new demo/throttled_ftpd.py script.
+
+ * Issue #52: implemented MLST and MLSD commands (RFC-3659).
+
+ * Issue #58: implemented OPTS command (RFC-2389).
+
+ * Issue #59: iterators are now used for calculating requests requiring long
+ time to complete (LIST and MLSD commands) drastically increasing the daemon
+ scalability when dealing with many connected clients.
+
+ * Issue #61: extended the set of assignable user permissions.
+
+Bugfixes:
+
+ * Issue #41: an unhandled exception occurred on QUIT if user was not yet
+ authenticated.
+
+ * Issue #43: hidden the server identifier returned in STAT response.
+
+ * Issue #44: a wrong response code was given on PORT in case of failed
+ connection attempt.
+
+ * Issue #45: a wrong response code was given on HELP if the provided argument
+ wasn't recognized as valid command.
+
+ * Issue #46: a wrong response code was given on PASV in case of unauthorized
+ FXP connection attempt.
+
+ * Issue #47: can't use FTPServer.max_cons option on Python 2.3.
+
+ * Issue #49: a "550 No such file or directory" was returned when LISTing
+ a directory containing a broken symbolic link.
+
+ * Issue #50: DTPHandler class did not respect what specified in
+ ac_out_buffer_size attribute.
+
+ * Issue #53: received strings having trailing white spaces was erroneously
+ stripped.
+
+ * Issue #54: LIST/NLST/STAT outputs are now sorted by file name.
+
+ * Issue #55: path traversal vulnerability in case of symbolic links escaping
+ user's home directory.
+
+ * Issue #56: can't rename broken symbolic links.
+
+ * Issue #57: invoking LIST/NLST over a symbolic link which points to a
+ direoctory shouldn't list its content.
+
+ * Issue #60: an unhandled IndexError exception error was raised in case of
+ certain bad formatted PORT requests.
+
+API changes since 0.2.0:
+
+ * New IteratorProducer and BufferedIteratorProducer classes have been added.
+
+ * DummyAuthorizer class changes:
+ * The permissions management has been changed and the set of available
+ permissions have been extended (see Issue #61). add_user() method
+ now accepts "eladfm" permissions beyond the old "r" and "w".
+ * r_perm() and w_perm() methods have been removed.
+ * New has_perm() and get_perms() methods have been added.
+
+ * AbstractedFS class changes:
+ * normalize() method has been renamed in ftpnorm().
+ * translate() method has been renamed in ftp2fs().
+ * New methods: fs2ftp(), stat(), lstat(), islink(), realpath(), lexists(),
+ validpath().
+ * get_list_dir(), get_stat_dir() and format_list() methods now return an
+ iterator object instead of a string.
+ * format_list() method has a new "ignore_err" keyword argument.
+
+ * global debug() function has been removed.
+
+
+Version: 0.2.0 - Date: 2007-09-17
+---------------------------------
+
+Major enhancements:
+
+ * Issue #5: it is now possible to set a maximum number of connecions and a
+ maximum number of connections from the same IP address.
+
+ * Issue #36: added support for FXP site-to-site transfer.
+
+ * Issue #39: added NAT/Firewall support with PASV (passive) mode connections.
+
+ * Issue #40: it is now possible to set a range of ports to use for passive
+ connections.
+
+RFC-related enhancements:
+
+ * Issue #6: accept TYPE AN and TYPE L8 as synonyms for TYPE ASCII and TYPE
+ Binary.
+
+ * Issue #7: a new USER command can now be entered at any point to begin the
+ login sequence again.
+
+ * Issue #10: HELP command arguments are now accepted.
+
+ * Issue #12: 554 error response is now returned on RETR/STOR if RESTart fails.
+
+ * Issue #15: STAT used with an argument now returns directory LISTing over the
+ command channel (RFC-959).
+
+Security enhancements:
+
+ * Issue #3: stop buffering when extremely long lines are received over the
+ command channel.
+
+ * Issue #11: data connection is now rejected in case a privileged port is
+ specified in PORT command.
+
+ * Issue #25: limited the number of attempts to find a unique filename when
+ processing STOU command.
+
+Usability enhancements:
+
+ * Provided an overridable attribute to easily set number of maximum login
+ attempts before disconnecting.
+
+ * Docstrings are now provided for almost every method and function.
+
+ * Issue #30: HELP response now includes the command syntax.
+
+ * Issue #31: a compact list of recognized commands is now provided on HELP.
+
+ * Issue #32: a detailed error message response is not returned to client in
+ case the transfer is interrupted for some unexpected reason.
+
+ * Issue #38: write access can now be optionally granted for anonymous user.
+
+Test suite enhancements:
+
+ * File creation/removal moved into setUp and tearDown methods to avoid leaving
+ behind orphaned temporary files in the event of a test suite failure.
+
+ * Issue #7: added test case for USER provided while already authenticated.
+
+ * Issue #7: added test case for REIN while a transfer is in progress.
+
+ * Issue #28: added ABOR tests.
+
+Bugfixes:
+
+ * Issue #4: socket's "reuse_address" feature was used after the socket's
+ binding.
+
+ * Issue #8: STOU string response didn't follow RFC-1123 specifications.
+
+ * Issue #9: corrected path traversal vulnerability affecting file-system path
+ translations.
+
+ * Issue #14: a wrong response code was returned on CDUP.
+
+ * Issue #17: SIZE is now rejected for not regular files.
+
+ * Issue #18: a wrong ABOR response code type was returned.
+
+ * Issue #19: watch for STOU preceded by REST which makes no sense.
+
+ * Issue #20: "attempted login" counter wasn't incremented on wrong username.
+
+ * Issue #21: STAT wasn't permitted if user wasn't authenticated yet.
+
+ * Issue #22: corrected memory leaks occurring on KeyboardInterrupt/SIGTERM.
+
+ * Issue #23: PASS wasn't rejected when user was already authenticated.
+
+ * Issue #24: Implemented a workaround over os.strerror() for those systems
+ where it is not available (Python CE).
+
+ * Issue #24: problem occurred on Windows when using '\\' as user's home
+ directory.
+
+ * Issue #26: select() in now used by default instead of poll() because of a
+ bug inherited from asyncore.
+
+ * Issue #33: some FTPHandler class attributes wasn't resetted on REIN.
+
+ * Issue #35: watch for APPE preceded by REST which makes no sense.
+
+
+Version: 0.1.1 - Date: 2007-03-27
+----------------------------------
+
+ * Port selection on PASV command has been randomized to prevent a remote user
+ to guess how many data connections are in progress on the server.
+
+ * Fixed bug in demo/unix_ftpd.py script.
+
+ * ftp_server.serve_forever now automatically re-use address if current system
+ is posix.
+
+ * License changed to MIT.
+
+
+Version: 0.1.0 - Date: 2007-02-26
+----------------------------------
+
+ * First proof of concept beta release.
diff --git a/chromium/third_party/pyftpdlib/src/INSTALL b/chromium/third_party/pyftpdlib/src/INSTALL
new file mode 100644
index 00000000000..a5165fb1e89
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/INSTALL
@@ -0,0 +1,4 @@
+Install
+=======
+
+See doc/install.html
diff --git a/chromium/third_party/pyftpdlib/src/LICENSE b/chromium/third_party/pyftpdlib/src/LICENSE
new file mode 100644
index 00000000000..a4d4ccaec05
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/LICENSE
@@ -0,0 +1,22 @@
+======================================================================
+Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that copyright notice and this permission
+notice appear in supporting documentation, and that the name of
+Giampaolo Rodola' not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+Giampaolo Rodola' DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+NO EVENT Giampaolo Rodola' BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+======================================================================
diff --git a/chromium/third_party/pyftpdlib/src/README b/chromium/third_party/pyftpdlib/src/README
new file mode 100644
index 00000000000..c23b1ae9b39
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/README
@@ -0,0 +1,12 @@
+
+About
+=====
+
+Python FTP server library provides a high-level portable interface to easily
+write asynchronous FTP servers with Python.
+pyftpdlib is currently the most complete RFC-959 FTP server implementation
+available for Python programming language.
+It is used in projects like Google Chromium and Bazaar and included in Linux
+Fedora and FreeBSD package repositories.
+
+Learn more by visiting: http://code.google.com/p/pyftpdlib/
diff --git a/chromium/third_party/pyftpdlib/src/demo/anti_flood_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/anti_flood_ftpd.py
new file mode 100644
index 00000000000..1b4565c9e7c
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/anti_flood_ftpd.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""
+A FTP server banning clients in case of commands flood.
+
+If client sends more than 300 requests per-second it will be
+disconnected and won't be able to re-connect for 1 hour.
+"""
+
+from pyftpdlib.ftpserver import FTPHandler, FTPServer, DummyAuthorizer, CallEvery
+
+
+class AntiFloodHandler(FTPHandler):
+
+ cmds_per_second = 300 # max number of cmds per second
+ ban_for = 60 * 60 # 1 hour
+ banned_ips = []
+
+ def __init__(self, *args, **kwargs):
+ super(AntiFloodHandler, self).__init__(*args, **kwargs)
+ self.processed_cmds = 0
+ self.pcmds_callback = CallEvery(1, self.check_processed_cmds)
+
+ def handle(self):
+ # called when client connects.
+ if self.remote_ip in self.banned_ips:
+ self.respond('550 you are banned')
+ self.close()
+ else:
+ super(AntiFloodHandler, self).handle()
+
+ def check_processed_cmds(self):
+ # called every second; checks for the number of commands
+ # sent in the last second.
+ if self.processed_cmds > self.cmds_per_second:
+ self.ban(self.remote_ip)
+ else:
+ self.processed_cmds = 0
+
+ def process_command(self, *args, **kwargs):
+ # increase counter for every received command
+ self.processed_cmds += 1
+ super(AntiFloodHandler, self).process_command(*args, **kwargs)
+
+ def ban(self, ip):
+ # ban ip and schedule next un-ban
+ if ip not in self.banned_ips:
+ self.log('banned IP %s for command flooding' % ip)
+ self.respond('550 you are banned for %s seconds' % self.ban_for)
+ self.close()
+ self.banned_ips.append(ip)
+
+ def unban(self, ip):
+ # unban ip
+ try:
+ self.banned_ips.remove(ip)
+ except ValueError:
+ pass
+ else:
+ self.log('unbanning IP %s' % ip)
+
+ def close(self):
+ super(AntiFloodHandler, self).close()
+ if not self.pcmds_callback.cancelled:
+ self.pcmds_callback.cancel()
+
+
+def main():
+ authorizer = DummyAuthorizer()
+ authorizer.add_user('user', '12345', '.', perm='elradfmw')
+ authorizer.add_anonymous('.')
+ handler = AntiFloodHandler
+ handler.authorizer = authorizer
+ ftpd = FTPServer(('', 21), handler)
+ ftpd.serve_forever(timeout=1)
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/demo/basic_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/basic_ftpd.py
new file mode 100644
index 00000000000..9743fe33005
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/basic_ftpd.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""A basic FTP server which uses a DummyAuthorizer for managing 'virtual
+users', setting a limit for incoming connections.
+"""
+
+import os
+
+from pyftpdlib import ftpserver
+
+
+def main():
+ # Instantiate a dummy authorizer for managing 'virtual' users
+ authorizer = ftpserver.DummyAuthorizer()
+
+ # Define a new user having full r/w permissions and a read-only
+ # anonymous user
+ authorizer.add_user('user', '12345', os.getcwd(), perm='elradfmwM')
+ authorizer.add_anonymous(os.getcwd())
+
+ # Instantiate FTP handler class
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+
+ # Define a customized banner (string returned when client connects)
+ ftp_handler.banner = "pyftpdlib %s based ftpd ready." %ftpserver.__ver__
+
+ # Specify a masquerade address and the range of ports to use for
+ # passive connections. Decomment in case you're behind a NAT.
+ #ftp_handler.masquerade_address = '151.25.42.11'
+ #ftp_handler.passive_ports = range(60000, 65535)
+
+ # Instantiate FTP server class and listen to 0.0.0.0:21
+ address = ('', 21)
+ ftpd = ftpserver.FTPServer(address, ftp_handler)
+
+ # set a limit for connections
+ ftpd.max_cons = 256
+ ftpd.max_cons_per_ip = 5
+
+ # start ftp server
+ ftpd.serve_forever()
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/demo/keycert.pem b/chromium/third_party/pyftpdlib/src/demo/keycert.pem
new file mode 100644
index 00000000000..bfa28def126
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/keycert.pem
@@ -0,0 +1,32 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L
+opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH
+fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB
+AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU
+D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA
+IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM
+oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0
+ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/
+loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j
+oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA
+z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq
+ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV
+q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD
+VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x
+IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT
+U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1
+NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl
+bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m
+dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj
+aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh
+m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8
+M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn
+fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC
+AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb
+08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx
+CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/
+iHkC6gGdBJhogs4=
+-----END CERTIFICATE-----
diff --git a/chromium/third_party/pyftpdlib/src/demo/md5_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/md5_ftpd.py
new file mode 100644
index 00000000000..e9b2540cfcb
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/md5_ftpd.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""A basic ftpd storing passwords as hash digests (platform independent).
+"""
+
+import os
+try:
+ from hashlib import md5
+except ImportError:
+ # backward compatibility with Python < 2.5
+ from md5 import new as md5
+
+from pyftpdlib import ftpserver
+
+
+class DummyMD5Authorizer(ftpserver.DummyAuthorizer):
+
+ def validate_authentication(self, username, password):
+ hash = md5(password).hexdigest()
+ return self.user_table[username]['pwd'] == hash
+
+
+def main():
+ # get a hash digest from a clear-text password
+ hash = md5('12345').hexdigest()
+ authorizer = DummyMD5Authorizer()
+ authorizer.add_user('user', hash, os.getcwd(), perm='elradfmw')
+ authorizer.add_anonymous(os.getcwd())
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+ address = ('', 21)
+ ftpd = ftpserver.FTPServer(address, ftp_handler)
+ ftpd.serve_forever()
+
+if __name__ == "__main__":
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/demo/throttled_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/throttled_ftpd.py
new file mode 100644
index 00000000000..c12bf9022c4
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/throttled_ftpd.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""An FTP server which uses the ThrottledDTPHandler class for limiting the
+speed of downloads and uploads.
+"""
+
+import os
+
+from pyftpdlib import ftpserver
+
+
+def main():
+ authorizer = ftpserver.DummyAuthorizer()
+ authorizer.add_user('user', '12345', os.getcwd(), perm='elradfmw')
+ authorizer.add_anonymous(os.getcwd())
+
+ dtp_handler = ftpserver.ThrottledDTPHandler
+ dtp_handler.read_limit = 30720 # 30 Kb/sec (30 * 1024)
+ dtp_handler.write_limit = 30720 # 30 Kb/sec (30 * 1024)
+
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+ # have the ftp handler use the alternative dtp handler class
+ ftp_handler.dtp_handler = dtp_handler
+
+ ftpd = ftpserver.FTPServer(('', 21), ftp_handler)
+ ftpd.serve_forever()
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/demo/tls_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/tls_ftpd.py
new file mode 100644
index 00000000000..71ed8205833
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/tls_ftpd.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""An RFC-4217 asynchronous FTPS server supporting both SSL and TLS.
+
+Requires PyOpenSSL module (http://pypi.python.org/pypi/pyOpenSSL).
+"""
+
+import os
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.handlers import TLS_FTPHandler
+
+CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ "keycert.pem"))
+
+def main():
+ authorizer = ftpserver.DummyAuthorizer()
+ authorizer.add_user('user', '12345', '.', perm='elradfmw')
+ authorizer.add_anonymous('.')
+ ftp_handler = TLS_FTPHandler
+ ftp_handler.certfile = CERTFILE
+ ftp_handler.authorizer = authorizer
+ # requires SSL for both control and data channel
+ #ftp_handler.tls_control_required = True
+ #ftp_handler.tls_data_required = True
+ ftpd = ftpserver.FTPServer(('', 8021), ftp_handler)
+ ftpd.serve_forever()
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/demo/unix_daemon.py b/chromium/third_party/pyftpdlib/src/demo/unix_daemon.py
new file mode 100644
index 00000000000..3a848b328ae
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/unix_daemon.py
@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""A basic unix daemon using the python-daemon library:
+http://pypi.python.org/pypi/python-daemon
+
+Example usages:
+
+ $ python unix_daemon.py start
+ $ python unix_daemon.py stop
+ $ python unix_daemon.py status
+ $ python unix_daemon.py # foreground (no daemon)
+ $ python unix_daemon.py --logfile /var/log/ftpd.log start
+ $ python unix_daemon.py --pidfile /var/run/ftpd.pid start
+
+This is just a proof of concept which demonstrates how to daemonize
+the FTP server.
+You might want to use this as an example and provide the necessary
+customizations.
+
+Parts you might want to customize are:
+ - UMASK, WORKDIR, HOST, PORT constants
+ - get_server() function (to define users and customize FTP handler)
+
+Authors:
+ - Ben Timby - btimby <at> gmail.com
+ - Giampaolo Rodola' - g.rodola <at> gmail.com
+"""
+
+from __future__ import with_statement
+
+import os
+import errno
+import sys
+import time
+import optparse
+import signal
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.authorizers import UnixAuthorizer
+
+# http://pypi.python.org/pypi/python-daemon
+import daemon
+import daemon.pidfile
+
+
+# overridable options
+HOST = ""
+PORT = 21
+PID_FILE = "/var/run/pyftpdlib.pid"
+LOG_FILE = "/var/log/pyftpdlib.log"
+WORKDIR = os.getcwd()
+UMASK = 0
+
+
+def pid_exists(pid):
+ """Return True if a process with the given PID is currently running."""
+ try:
+ os.kill(pid, 0)
+ except OSError, e:
+ return e.errno == errno.EPERM
+ else:
+ return True
+
+def get_pid():
+ """Return the PID saved in the pid file if possible, else None."""
+ try:
+ with open(PID_FILE) as f:
+ return int(f.read().strip())
+ except IOError, err:
+ if err.errno != errno.ENOENT:
+ raise
+
+def stop():
+ """Keep attempting to stop the daemon for 5 seconds, first using
+ SIGTERM, then using SIGKILL.
+ """
+ pid = get_pid()
+ if not pid or not pid_exists(pid):
+ print "daemon not running"
+ return
+ sig = signal.SIGTERM
+ i = 0
+ while True:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+ try:
+ os.kill(pid, sig)
+ except OSError, e:
+ if e.errno == errno.ESRCH:
+ print "\nstopped (pid %s)" % pid
+ return
+ else:
+ raise
+ i += 1
+ if i == 25:
+ sig = signal.SIGKILL
+ elif i == 50:
+ sys.exit("\ncould not kill daemon (pid %s)" % pid)
+ time.sleep(0.1)
+
+def status():
+ """Print daemon status and exit."""
+ pid = get_pid()
+ if not pid or not pid_exists(pid):
+ print "daemon not running"
+ else:
+ print "daemon running with pid %s" % pid
+ sys.exit(0)
+
+def get_server():
+ """Return a pre-configured FTP server instance."""
+ # when daemonized, it seems we need to flush() stdout explicitly
+ # in order to get the log file written in real time
+ def log(s):
+ sys.stdout.write(s + "\n")
+ sys.stdout.flush()
+ ftpserver.log = ftpserver.logline = log
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = UnixAuthorizer()
+ server = ftpserver.FTPServer((HOST, PORT), ftp_handler)
+ return server
+
+def daemonize():
+ """A wrapper around python-daemonize context manager."""
+ pid = get_pid()
+ if pid and pid_exists(pid):
+ sys.exit('daemon already running (pid %s)' % pid)
+ # instance FTPd before daemonizing, so that in case of problems we
+ # get an exception here and exit immediately
+ server = get_server()
+ logfile = open(LOG_FILE, 'a+')
+ ctx = daemon.DaemonContext(
+ stdout=logfile,
+ stderr=logfile,
+ working_directory=WORKDIR,
+ umask=UMASK,
+ pidfile=daemon.pidfile.TimeoutPIDLockFile(PID_FILE),
+ files_preserve=[server],
+ )
+ with ctx:
+ server.serve_forever()
+
+def main():
+ global PID_FILE, LOG_FILE
+ USAGE = "python [-p PIDFILE] [-l LOGFILE]\n\n" \
+ "Commands:\n - start\n - stop\n - status"
+ parser = optparse.OptionParser(usage=USAGE)
+ parser.add_option('-l', '--logfile', dest='logfile',
+ help='the log file location')
+ parser.add_option('-p', '--pidfile', dest='pidfile', default=PID_FILE,
+ help='file to store/retreive daemon pid')
+ options, args = parser.parse_args()
+
+ if options.pidfile:
+ PID_FILE = options.pidfile
+ if options.logfile:
+ LOG_FILE = options.logfile
+
+ if not args:
+ server = get_server()
+ server.serve_forever()
+ else:
+ if len(args) != 1:
+ sys.exit('too many commands')
+ elif args[0] == 'start':
+ daemonize()
+ elif args[0] == 'stop':
+ stop()
+ elif args[0] == 'status':
+ status()
+ else:
+ sys.exit('invalid command')
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/chromium/third_party/pyftpdlib/src/demo/unix_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/unix_ftpd.py
new file mode 100644
index 00000000000..2ccbae857d1
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/unix_ftpd.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""A FTPd using local UNIX account database to authenticate users.
+
+It temporarily impersonate the system users every time they are going
+to perform a filesystem operations.
+"""
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.authorizers import UnixAuthorizer
+from pyftpdlib.contrib.filesystems import UnixFilesystem
+
+
+def main():
+ authorizer = UnixAuthorizer(rejected_users=["root"], require_valid_shell=True)
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+ ftp_handler.abstracted_fs = UnixFilesystem
+ address = ('', 21)
+ ftpd = ftpserver.FTPServer(address, ftp_handler)
+ ftpd.serve_forever()
+
+if __name__ == "__main__":
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/demo/winnt_ftpd.py b/chromium/third_party/pyftpdlib/src/demo/winnt_ftpd.py
new file mode 100644
index 00000000000..3ac2c28aedf
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/demo/winnt_ftpd.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""A ftpd using local Windows NT account database to authenticate users
+(users must already exist).
+
+It also provides a mechanism to (temporarily) impersonate the system
+users every time they are going to perform filesystem operations.
+"""
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.authorizers import WindowsAuthorizer
+
+
+def main():
+ authorizer = WindowsAuthorizer()
+ # Use Guest user with empty password to handle anonymous sessions.
+ # Guest user must be enabled first, empty password set and profile
+ # directory specified.
+ #authorizer = WindowsAuthorizer(anonymous_user="Guest", anonymous_password="")
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+ address = ('', 21)
+ ftpd = ftpserver.FTPServer(address, ftp_handler)
+ ftpd.serve_forever()
+
+if __name__ == "__main__":
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/doc/adoptions.lnk.html b/chromium/third_party/pyftpdlib/src/doc/adoptions.lnk.html
new file mode 100644
index 00000000000..e979ee31cd9
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/adoptions.lnk.html
@@ -0,0 +1,4 @@
+<a href="http://code.google.com/p/pyftpdlib"><img id="logo" src="logo.png" /></a>
+<title>Redirecting to Adoptions List...</title>
+<p>Redirecting to: <a href="http://code.google.com/p/pyftpdlib/wiki/Adoptions">http://code.google.com/p/pyftpdlib/wiki/Adoptions</a><br></p>
+<p>You'll be redirected in 3 seconds...</p>
diff --git a/chromium/third_party/pyftpdlib/src/doc/faq.html b/chromium/third_party/pyftpdlib/src/doc/faq.html
new file mode 100644
index 00000000000..4e7f81804c6
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/faq.html
@@ -0,0 +1,167 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style>
+.section_anchor {
+ font-size:0px;
+}
+</style>
+
+
+
+
+
+ <title>FAQ</title>
+ </head><body>
+
+
+
+
+ <div id="wikicontent">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+
+ <td class="vt" id="wikimaincol" width="100%">
+
+ <div id="wikiheader" style="margin-bottom: 1em;">
+
+ &nbsp;
+ <span style="font-size: 120%; font-weight: bold;">FAQ</span>
+ &nbsp;
+
+
+ <div style="font-style: italic; margin-top: 3px;">Frequently Asked Questions</div>
+
+ </div>
+ <h1><a name="Table_of_contents"></a>Table of contents<a href="#Table_of_contents" class="section_anchor">¶</a></h1><p></p><ul><li><a href="#Table_of_contents">Table of contents</a></li><li><a href="#Introduction">Introduction</a></li><ul><li><a href="#What_is_pyftpdlib?">What is pyftpdlib?</a></li><li><a href="#What_is_Python?">What is Python?</a></li><li><a href="#I%27m_not_a_python_programmer._Can_I_use_it_anyway?">I'm not a python programmer. Can I use it anyway?</a></li><li><a href="#Documentation">Documentation</a></li><li><a href="#Mailing_lists">Mailing lists</a></li><li><a href="#Bug_reporting">Bug reporting</a></li></ul><li><a href="#Installing_and_compatibility">Installing and compatibility</a></li><ul><li><a href="#How_do_I_install_pyftpdlib?">How do I install pyftpdlib?</a></li><li><a href="#Which_Python_versions_are_compatible?">Which Python versions are compatible?</a></li><li><a href="#What_about_Python_3.x?">What about Python 3.x?</a></li><li><a href="#On_which_platforms_can_pyftpdlib_be_used?">On which platforms can pyftpdlib be used?</a></li></ul><li><a href="#Usage">Usage</a></li><ul><li><a href="#How_can_I_run_long-running_tasks_without_blocking_the_server?">How can I run long-running tasks without blocking the server?</a></li><li><a href="#Why_do_I_get_socket.error_%22Permission_denied%22_error_on">Why do I get socket.error "Permission denied" error on ftpd starting?</a></li><li><a href="#How_can_I_prevent_the_server_version_from_being_displayed?">How can I prevent the server version from being displayed?</a></li><li><a href="#Can_control_upload/download_ratios?">Can control upload/download ratios?</a></li><li><a href="#Are_there_ways_to_limit_connections?">Are there ways to limit connections?</a></li><li><a href="#I%27m_behind_a_NAT_/_gateway">I'm behind a NAT / gateway</a></li><li><a href="#What_is_FXP?">What is FXP?</a></li><li><a href="#Does_pyftpdlib_support_FXP?">Does pyftpdlib support FXP?</a></li><li><a href="#Why_timestamps_shown_by_MDTM_and_ls_commands_%28LIST,_MLSD,_MLST%29">Why timestamps shown by MDTM and ls commands (LIST, MLSD, MLST) are wrong?</a></li></ul><li><a href="#Implementation">Implementation</a></li><ul><li><a href="#Globbing_/_STAT_command_implementation">Globbing / STAT command implementation</a></li><li><a href="#ASCII_transfers_/_SIZE_command_implementation">ASCII transfers / SIZE command implementation</a></li><li><a href="#IPv6_support">IPv6 support</a></li><li><a href="#How_do_I_install_IPv6_support_on_my_system?">How do I install IPv6 support on my system?</a></li><li><a href="#Can_pyftpdlib_be_integrated_with_%22real%22_users_existing">Can pyftpdlib be integrated with "real" users existing on the system?</a></li><li><a href="#Does_pyftpdlib_support_FTP_over_TLS/SSL_%28FTPS%29">Does pyftpdlib support FTP over TLS/SSL (FTPS)</a></li><li><a href="#What_about_SITE_commands?">What about SITE commands?</a></li></ul></ul> <p></p><h1><a name="Introduction"></a>Introduction<a href="#Introduction" class="section_anchor">¶</a></h1><h2><a name="What_is_pyftpdlib?"></a>What is pyftpdlib?<a href="#What_is_pyftpdlib?" class="section_anchor">¶</a></h2><p>pyftpdlib is a high-level library to easily write asynchronous portable FTP servers with <a href="http://www.python.org/" rel="nofollow">Python</a>. </p><h2><a name="What_is_Python?"></a>What is Python?<a href="#What_is_Python?" class="section_anchor">¶</a></h2><p>Python is an interpreted, interactive, object-oriented, easy-to-learn programming language. It is often compared to <i>Tcl, Perl, Scheme</i> or <i>Java</i>. </p><h2><a name="I'm_not_a_python_programmer._Can_I_use_it_anyway?"></a>I'm not a python programmer. Can I use it anyway?<a href="#I%27m_not_a_python_programmer._Can_I_use_it_anyway?" class="section_anchor">¶</a></h2><p>Yes.
+ pyftpdlib is a fully working FTP server implementation that can be run
+"as is". For example you could run an anonymous ftp server from cmd-line
+ by running: </p><pre class="prettyprint">giampaolo@ubuntu:~$ sudo python -m pyftpdlib.ftpserver
+Serving FTP on 0.0.0.0:21</pre><p>This is useful in case you want a
+quick and dirty way to share a directory without, say, installing and
+configuring samba. Starting from version 0.6.0 options can be passed to
+the command line (see <tt>python -m pyftpdlib.ftpserver --help</tt> to see all available options). Examples: </p><p>Anonymous FTP server with write access: </p><pre class="prettyprint">giampaolo@ubuntu:~$ sudo python -m pyftpdlib.ftpserver -w
+/usr/local/lib/python2.6/site-packages/pyftpdlib/ftpserver.py:520: RuntimeWarning: Write permissions assigned to anonymous user.
+ RuntimeWarning)
+Serving FTP on 0.0.0.0:21</pre><p>Listen on a different ip/port: </p><pre class="prettyprint">giampaolo@ubuntu:~$ python -m pyftpdlib.ftpserver -i 127.0.0.1 -p 8021
+Serving FTP on 127.0.0.1:8021</pre><p>Customizing ftpd for basic tasks
+like adding users or deciding where log file should be placed is mostly
+simply editing variables. This is basically like learning how to edit a
+common unix ftpd.conf file and doesn't really require Python knowledge.
+Customizing ftpd more deeply requires a python script which imports
+pyftpdlib to be written separately. An example about how this could be
+done are the scripts contained in the <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/demo" rel="nofollow">demo directory</a>. </p><h2><a name="Documentation"></a>Documentation<a href="#Documentation" class="section_anchor">¶</a></h2><p><a href="http://code.google.com/p/pyftpdlib/" rel="nofollow">http://code.google.com/p/pyftpdlib/</a> is the primary source for all information about the project including <a href="http://code.google.com/p/pyftpdlib/wiki/Install" rel="nofollow">Install instructions</a>, <a href="http://code.google.com/p/pyftpdlib/wiki/Tutorial" rel="nofollow">Tutorial</a>, <a href="http://code.google.com/p/pyftpdlib/wiki/RFCsCompliance" rel="nofollow">RFCs Compliance paper</a>, <a href="http://code.google.com/p/pyftpdlib/w/list" rel="nofollow">Wikis</a> and the <a href="http://code.google.com/p/pyftpdlib/issues/list" rel="nofollow">Bug Tracker</a>. </p><h2><a name="Mailing_lists"></a>Mailing lists<a href="#Mailing_lists" class="section_anchor">¶</a></h2><p>There are a number of mailing lists for pyftpdlib: </p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Name</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>E-mail</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Web Interface</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <a href="http://groups.google.com/group/pyftpdlib" rel="nofollow">pyftpdlib</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> pyftpdlib@googlegroups.com </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <a href="http://groups.google.com/group/pyftpdlib/topics" rel="nofollow">topics</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> This is intended for end user support. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <a href="http://groups.google.com/group/pyftpdlib-commit" rel="nofollow">pyftpdlib-commit</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> pyftpdlib-commits@googlegroups.com </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <a href="http://groups.google.com/group/pyftpdlib-commit/topics" rel="nofollow">topics</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ This list receives all change notifications for code in the Subversion
+repository. Unless you're a pyftpdlib developer you will probably not be
+ interested in it. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <a href="http://groups.google.com/group/pyftpdlib-issues" rel="nofollow">pyftpdlib-issues</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> pyftpdlib-issues@googlegroups.com </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <a href="http://groups.google.com/group/pyftpdlib-issues/topics" rel="nofollow">topics</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> This list receives all change notifications from the <a href="http://code.google.com/p/pyftpdlib/issues/list" rel="nofollow">Bug Tracker</a>. Unless you are involved into pyftpdlib development you will probably not find this useful. </td></tr> </tbody></table></p><h2><a name="Bug_reporting"></a>Bug reporting<a href="#Bug_reporting" class="section_anchor">¶</a></h2><p>Bug reports should be made via Google Code <a href="http://code.google.com/p/pyftpdlib/issues/list" rel="nofollow">Issue Tracker</a>. Patches should be attached to the appropriate bug and not mailed directly to the mailing lists or any given team member. </p><hr><h1><a name="Installing_and_compatibility"></a>Installing and compatibility<a href="#Installing_and_compatibility" class="section_anchor">¶</a></h1><h2><a name="How_do_I_install_pyftpdlib?"></a>How do I install pyftpdlib?<a href="#How_do_I_install_pyftpdlib?" class="section_anchor">¶</a></h2><p>If you are not new to Python you probably don't need that, otherwise follow the <a href="http://code.google.com/p/pyftpdlib/wiki/Install" rel="nofollow">instructions</a>. </p><h2><a name="Which_Python_versions_are_compatible?"></a>Which Python versions are compatible?<a href="#Which_Python_versions_are_compatible?" class="section_anchor">¶</a></h2><p><strong>2.4</strong>, <strong>2.5</strong>, <strong>2.6</strong> and <strong>2.7</strong>. Python 2.3 support has been removed starting from version 0.6.0. The latest version supporting Python 2.3 is <a href="http://code.google.com/p/pyftpdlib/downloads/detail?name=pyftpdlib-0.5.2.tar.gz" rel="nofollow">pyftpdlib 0.5.2</a>. </p><h2><a name="What_about_Python_3.x?"></a>What about Python 3.x?<a href="#What_about_Python_3.x?" class="section_anchor">¶</a></h2><p>Python
+ 3.x is not yet covered because I still have to figure out how to deal
+with the str/bytes and encoding differences introduced in the new Python
+ version =). A porting to Python 3.x is surely in plan, anyway (see <a href="http://code.google.com/p/pyftpdlib/issues/detail?id=76" rel="nofollow">issue #76</a>. </p><h2><a name="On_which_platforms_can_pyftpdlib_be_used?"></a>On which platforms can pyftpdlib be used?<a href="#On_which_platforms_can_pyftpdlib_be_used?" class="section_anchor">¶</a></h2><p>pyftpdlib should work on any platform where <strong><i>select()</i></strong> or <strong><i>poll()</i></strong> system calls are available and on any Python implementation which refers to <strong>cPython 2.4</strong> or superior (e.g cPython 2.6 or PythonCE 2.5). The development team has mainly tested it under various <strong>Linux</strong>, <strong>Windows</strong>, <strong>OS X</strong> and <strong>FreeBSD</strong> systems. For FreeBSD is also available a <a href="http://www.freshports.org/ftp/py-pyftpdlib/" rel="nofollow">pre-compiled package</a> maintained by Li-Wen Hsu &lt;lwhsu@freebsd.org&gt;. Other Python implementation like <strong><a href="http://pythonce.sourceforge.net/" rel="nofollow">PythonCE</a></strong> are known to work with pyftpdlib and every new version is usually tested against it. pyftpdlib currently does not work on <strong><a href="http://www.jython.org/" rel="nofollow">Jython</a></strong>
+ since the latest Jython release refers to CPython 2.2.x serie. The best
+ way to know whether pyftpdlib works on your platform is installing it
+and running its test suite. </p><hr><h1><a name="Usage"></a>Usage<a href="#Usage" class="section_anchor">¶</a></h1><h2><a name="How_can_I_run_long-running_tasks_without_blocking_the_server?"></a>How can I run long-running tasks without blocking the server?<a href="#How_can_I_run_long-running_tasks_without_blocking_the_server?" class="section_anchor">¶</a></h2><p>pyftpdlib is an <strong>asynchronous</strong>
+ FTP server. That means that if you need to run a time consuming task
+you have to use a separate Python process or thread for the actual
+processing work otherwise the entire asynchronous loop will be blocked. </p><p>Let's
+ suppose you want to implement a long-running task every time the server
+ receives a file. The code snippet below shows the correct way to do it
+by using a thread. </p><p>Notice how we first set <tt>FTPHandler.sleeping</tt> attribute to <tt>True</tt>, then we set it back to <tt>False</tt> when the long-running task has completed.<br> By setting <tt>sleeping</tt> attribute to <tt>False</tt>
+ we temporarily "sleep" the channel which won't be able to send or
+receive any more data and won't be closed (disconnected) as long as we
+don't set it back to <tt>True</tt>. This is fundamental when working with threads to avoid race conditions, dead locks etc. </p><pre class="prettyprint">class MyHandler(ftpserver.FTPHandler):
+
+ def on_file_received(self, file):
+ """Called every time a file has been received"""
+
+ def blocking_task():
+ time.sleep(5)
+ self.sleeping = False
+
+ self.sleeping = True
+ threading.Thread(target=blocking_task).start()</pre><h2><a name="Why_do_I_get_socket.error_&quot;Permission_denied&quot;_error_on"></a>Why do I get socket.error "Permission denied" error on ftpd starting?<a href="#Why_do_I_get_socket.error_%22Permission_denied%22_error_on" class="section_anchor">¶</a></h2><p>Probably because you're on a Unix system and you're trying to start ftpd as an unprivileged user. <i>ftpserver.py</i>
+ binds on port 21 by default and only super-user account (e.g. root) can
+ bind sockets on such ports. If you want to bind ftpd as non-privileged
+user you should set a port higher than 1024. </p><h2><a name="How_can_I_prevent_the_server_version_from_being_displayed?"></a>How can I prevent the server version from being displayed?<a href="#How_can_I_prevent_the_server_version_from_being_displayed?" class="section_anchor">¶</a></h2><p>Just modify <tt>banner</tt> attribute of <tt>FTPHandler</tt> class. </p><h2><a name="Can_control_upload/download_ratios?"></a>Can control upload/download ratios?<a href="#Can_control_upload/download_ratios?" class="section_anchor">¶</a></h2><p>Yes. Starting from version 0.5.2 ftpserver.py provides a new class called <tt>ThrottledDTPHandler</tt>. You can set speed limits by modifying <tt>read_limit</tt> and <tt>write_limit</tt> class attributes as it is shown in <a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/throttled_ftpd.py" rel="nofollow">throttled_ftpd.py</a> demo script. </p><h2><a name="Are_there_ways_to_limit_connections?"></a>Are there ways to limit connections?<a href="#Are_there_ways_to_limit_connections?" class="section_anchor">¶</a></h2><p><tt>FTPServer</tt> class comes with two overridable attributes defaulting to zero (no limit): <tt>max_cons</tt>, which sets a limit for maximum simultaneous connection to handle by ftpd and <tt>max_cons_per_ip</tt>
+ which set a limit for connections from the same IP address. Overriding
+these variables is always recommended to avoid DoS attacks. </p><h2><a name="I'm_behind_a_NAT_/_gateway"></a>I'm behind a NAT / gateway<a href="#I%27m_behind_a_NAT_/_gateway" class="section_anchor">¶</a></h2><p>When
+ behind a NAT a ftp server needs to replace the IP local address
+displayed in PASV replies and instead use the public address of the NAT
+to allow client to connect. By overriding <tt>masquerade_address</tt> attribute of <tt>FTPHandler</tt>
+ class you will force pyftpdlib to do such replacement. However, one
+problem still exists. The passive FTP connections will use ports from
+1024 and up, which means that you must forward all ports 1024-65535 from
+ the NAT to the FTP server! And you have to allow many (possibly)
+dangerous ports in your firewalling rules! To resolve this, simply
+override <tt>passive_ports</tt> attribute of <tt>FTPHandler</tt> class to control what ports pyftpdlib will use for its passive data transfers. Value expected by <tt>passive_ports</tt>
+ attribute is a list of integers (e.g. range(60000, 65535)) indicating
+which ports will be used for initializing the passive data channel. In
+case you run a FTP server with multiple private IP addresses behind a
+NAT firewall with multiple public IP addresses you can use <tt>FTPHandler.masquerade_address_map</tt> option which allows you to define multiple 1 to 1 mappings (<strong><i>New in 0.6.0</i></strong>). </p><h2><a name="What_is_FXP?"></a>What is FXP?<a href="#What_is_FXP?" class="section_anchor">¶</a></h2><p>FXP is part of the name of a popular Windows FTP client: <a href="http://www.flashfxp.com/" rel="nofollow">http://www.flashfxp.com</a>.
+ This client has made the name "FXP" commonly used as a synonym for
+site-to-site FTP transfers, for transferring a file between two remote
+FTP servers without the transfer going through the client's host.
+Sometimes "FXP" is referred to as a protocol; in fact, it is not. The
+site-to-site transfer capability was deliberately designed into <a href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a>. More info can be found here: <a href="http://www.proftpd.org/docs/howto/FXP.html" rel="nofollow">http://www.proftpd.org/docs/howto/FXP.html</a>. </p><h2><a name="Does_pyftpdlib_support_FXP?"></a>Does pyftpdlib support FXP?<a href="#Does_pyftpdlib_support_FXP?" class="section_anchor">¶</a></h2><p>Yes. It is disabled by default for security reasons (see <a href="http://gim.org.pl/rfcs/rfc2577.html" rel="nofollow">RFC-2257</a> and <a href="http://www.cert.org/advisories/CA-1997-27.html" rel="nofollow">FTP bounce attack description</a>) but in case you want to enable it just set to True the <tt>permit_foreign_addresses</tt> attribute of <tt>FTPHandler</tt> class. </p><h2><a name="Why_timestamps_shown_by_MDTM_and_ls_commands_(LIST,_MLSD,_MLST)"></a>Why timestamps shown by MDTM and ls commands (LIST, MLSD, MLST) are wrong?<a href="#Why_timestamps_shown_by_MDTM_and_ls_commands_%28LIST,_MLSD,_MLST%29" class="section_anchor">¶</a></h2><p>If
+ by "wrong" you mean "different from the timestamp of that file on my
+client machine", then that is the expected behavior. Starting from
+version 0.6.0 pyftpdlib uses <a href="http://en.wikipedia.org/wiki/Greenwich_Mean_Time" rel="nofollow">GMT times</a> as recommended in <a href="http://tools.ietf.org/html/rfc3659" rel="nofollow">RFC-3659</a>. In case you want such commands to report local times instead just set the <tt>FTPHandler.use_gmt_times</tt> attribute to <tt>False</tt>. For further information you might want to take a look at <a href="http://www.proftpd.org/docs/howto/Timestamps.html" rel="nofollow">this</a> Proftpd FAQ. </p><hr><h1><a name="Implementation"></a>Implementation<a href="#Implementation" class="section_anchor">¶</a></h1><h2><a name="Globbing_/_STAT_command_implementation"></a>Globbing / STAT command implementation<a href="#Globbing_/_STAT_command_implementation" class="section_anchor">¶</a></h2><p>Globbing
+ is a common Unix shell mechanism for expanding wildcard patterns to
+match multiple filenames. When an argument is provided to the <strong>STAT</strong> command, ftpd should return directory listing over the command channel. <a href="http://tools.ietf.org/html/rfc959" rel="nofollow">RFC-959</a>
+ does not explicitly mention globbing; this means that FTP servers are
+not required to support globbing in order to be compliant. However,
+many FTP servers do support globbing as a measure of convenience for FTP
+ clients and users. In order to search for and match the given globbing
+expression, the code has to search (possibly) many directories, examine
+each contained filename, and build a list of matching files in memory.
+Since this operation can be quite intensive, both CPU- and memory-wise,
+pyftpdlib <i>does not</i> support globbing. </p><h2><a name="ASCII_transfers_/_SIZE_command_implementation"></a>ASCII transfers / SIZE command implementation<a href="#ASCII_transfers_/_SIZE_command_implementation" class="section_anchor">¶</a></h2><p>Properly
+ handling the SIZE command when TYPE ASCII is used would require to scan
+ the entire file to perform the ASCII translation logic
+(file.read().replace(os.linesep, '\r\n')) and then calculating the len
+of such data which may be different than the actual size of the file on
+the server. Considering that calculating such result could be very
+resource-intensive it could be easy for a malicious client to try a DoS
+attack, thus pyftpdlib rejects SIZE when the current TYPE is ASCII.
+However, clients in general should not be resuming downloads in ASCII
+mode. Resuming downloads in binary mode is the recommended way as
+specified in <a href="http://tools.ietf.org/html/rfc3659" rel="nofollow">RFC-3659</a>. </p><h2><a name="IPv6_support"></a>IPv6 support<a href="#IPv6_support" class="section_anchor">¶</a></h2><p>Starting from version 0.4.0 pyftpdlib <i>supports</i> IPv6 (<a href="http://tools.ietf.org/html/rfc2428" rel="nofollow">RFC-2428</a>).
+ If you use IPv6 and want your FTP daemon to do so just pass a valid
+IPv6 address to the FTPServer class constructor. Example: </p><pre class="prettyprint">&gt;&gt;&gt; from pyftpdlib import ftpserver
+&gt;&gt;&gt; address = ("::1", 21) # listen on localhost, port 21
+&gt;&gt;&gt; ftpd = ftpserver.FTPServer(address, ftpserver.FTPHandler)
+&gt;&gt;&gt; ftpd.serve_forever()
+Serving FTP on ::1:21</pre><p>If your OS (for example: all recent UNIX
+systems) have an hybrid dual-stack IPv6/IPv4 implementation the code
+above will listen on both IPv4 and IPv6 by using a single IPv6 socket (<i><strong>New in 0.6.0</strong></i>). </p><h2><a name="How_do_I_install_IPv6_support_on_my_system?"></a>How do I install IPv6 support on my system?<a href="#How_do_I_install_IPv6_support_on_my_system?" class="section_anchor">¶</a></h2><p>If
+ you want to install IPv6 support on Linux run "modprobe ipv6", then
+"ifconfig". This should display the loopback adapter, with the address
+"::1". You should then be able to listen the server on that address, and
+ connect to it. </p><p>On Windows (XP SP2 and higher) run "netsh int ipv6 install". Again, you should be able to use IPv6 loopback afterwards. </p><h2><a name="Can_pyftpdlib_be_integrated_with_&quot;real&quot;_users_existing"></a>Can pyftpdlib be integrated with "real" users existing on the system?<a href="#Can_pyftpdlib_be_integrated_with_%22real%22_users_existing" class="section_anchor">¶</a></h2><p>Yes. Starting from version 0.6.0 pyftpdlib provides the new <tt>UnixAuthorizer</tt> and <tt>WindowsAuthorizer</tt>
+ classes. By using them pyftpdlib can look into the system account
+database to authenticate users. They also assume the id of real users
+every time the FTP server is going to access the filesystem (e.g. for
+creating or renaming a file) the authorizer will temporarily impersonate
+ the currently logged on user, execute the filesystem call and then
+switch back to the user who originally started the server. Example UNIX
+and Windows FTP servers contained in the <a href="http://code.google.com/p/pyftpdlib/source/browse/#svn/trunk/demo" rel="nofollow">demo directory</a> shows how to use <tt>UnixAuthorizer</tt> and <tt>WindowsAuthorizer</tt> classes. </p><h2><a name="Does_pyftpdlib_support_FTP_over_TLS/SSL_(FTPS)"></a>Does pyftpdlib support FTP over TLS/SSL (FTPS)<a href="#Does_pyftpdlib_support_FTP_over_TLS/SSL_%28FTPS%29" class="section_anchor">¶</a></h2><p>Yes, starting from version 0.6.0, see: <a href="http://code.google.com/p/billiejoex/wiki/Tutorial#3.7_-_FTPS_%28FTP_over_TLS/SSL%29_server" rel="nofollow">http://code.google.com/p/billiejoex/wiki/Tutorial#3.7_-_FTPS_(FTP_over_TLS/SSL)_server</a> </p><h2><a name="What_about_SITE_commands?"></a>What about SITE commands?<a href="#What_about_SITE_commands?" class="section_anchor">¶</a></h2><p>No SITE commands aside from <strong>SITE HELP</strong> are implemented by default. The user willing to add support for a specific SITE command (e.g. <strong>SITE CHMOD</strong>) has to define a new <tt>ftp_SITE_%CMD%</tt> method in the <tt>FTPHandler</tt> subclass and add a new entry in <tt>ftpserver.proto_cmds</tt> dictionary. Example: </p><pre class="prettyprint">from pyftpdlib import ftpserver
+
+prop = (None, True, None, False, 'Syntax: SITE CHMOD &lt;SP&gt; path (change file permission).')
+ftpserver.proto_cmds['SITE CHMOD'] = ftpserver._CommandProperty(*prop)
+
+class CustomizedFTPHandler(ftpserver.FTPHandler):
+
+ def ftp_SITE_CHMOD(self, line):
+ """Change file permissions."""
+ ...</pre>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+
+
+
+
+<script type="text/javascript" src="faq_files/dit_scripts.js"></script>
+
+
+
+ </body></html>
diff --git a/chromium/third_party/pyftpdlib/src/doc/index.html b/chromium/third_party/pyftpdlib/src/doc/index.html
new file mode 100644
index 00000000000..94e0a2b9b90
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/index.html
@@ -0,0 +1,100 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style>
+.section_anchor {
+ font-size:0px;
+}
+</style>
+
+
+
+ <title>Home</title>
+ </head><body>
+
+
+
+
+ <div id="wikicontent">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+
+ <td class="vt" id="wikimaincol" width="100%">
+
+ <div id="wikiheader" style="margin-bottom: 1em;">
+
+ &nbsp;
+ <span style="font-size: 120%; font-weight: bold;">Home</span>
+ &nbsp;
+
+
+ </div>
+ <h1><a name="Python_FTP_server_library_(pyftpdlib)"></a>Python FTP server library (pyftpdlib)<a href="#Python_FTP_server_library_%28pyftpdlib%29" class="section_anchor">¶</a></h1><h2><a name="About"></a>About<a href="#About" class="section_anchor">¶</a></h2><p>Python FTP server library provides a high-level portable interface to easily write asynchronous FTP servers with Python.<br>pyftpdlib is currently the most complete <a href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a> FTP server implementation available for <a href="http://www.python.org/" rel="nofollow">Python</a> programming language.<br>It is used in projects like <a href="http://www.code.google.com/chromium/" rel="nofollow">Google Chromium</a> and <a href="http://bazaar-vcs.org/" rel="nofollow">Bazaar</a> and included in <a href="http://packages.debian.org/sid/python-pyftpdlib" rel="nofollow">Debian</a>, <a href="https://admin.fedoraproject.org/pkgdb/packages/name/pyftpdlib" rel="nofollow">Fedora</a> and <a href="http://www.freshports.org/ftp/py-pyftpdlib/" rel="nofollow">FreeBSD</a> package repositories. </p><h2><a name="Features"></a>Features<a href="#Features" class="section_anchor">¶</a></h2><ul><li>Support for <strong><a href="http://en.wikipedia.org/wiki/FTPS" rel="nofollow">FTPS</a></strong> (FTP over TLS/SSL). <i><strong>New in 0.6.0</strong></i> </li><li>Native support for virtual users and virtual filesystem. </li><li>Support for recent FTP commands like <strong>MLSD</strong> and <strong>MLST</strong> (<a href="http://www.faqs.org/rfcs/rfc3659.html" rel="nofollow">RFC-3659</a>). </li><li>Support for <strong><a href="http://www.proftpd.org/docs/howto/FXP.html" rel="nofollow">FXP</a></strong>, site-to-site transfers. </li><li>Support for <strong>IPv6</strong> (<a href="ftp://ftp.rfc-editor.org/in-notes/rfc2428.txt" rel="nofollow">RFC-2428</a>). </li><li>NAT/Firewall support with <strong>PASV/EPSV</strong> passive mode connections. </li><li>Bandwidth throttling. </li><li>Support for resumed transfers. </li><li>Per-user permissions configurability. </li><li>Maximum connections limit. </li><li>Per-source-IP limits. </li><li>Configurable idle timeouts for both control and data channels. </li><li>Compact: main library is distributed as a single stand-alone module (<a href="http://pyftpdlib.googlecode.com/svn/trunk/pyftpdlib/ftpserver.py" rel="nofollow">ftpserver.py</a>). </li><li>High portability: </li><ul><li>Entirely written in pure Python, no third party modules are used. It works on any system where <i>select( )</i> or <i>poll( )</i> is available. </li><li>Extremely flexible system of "authorizers" able to manage both "virtual" and "real" users on different platforms (<strong>Windows</strong>, <strong>UNIX</strong>, <strong>OSX</strong>). </li><li>Works with Python 2.4, 2.5, 2.6 and 2.7. </li></ul></ul><h2><a name="Quick_start"></a>Quick start<a href="#Quick_start" class="section_anchor">¶</a></h2><pre class="prettyprint">&gt;&gt;&gt; from pyftpdlib import ftpserver
+&gt;&gt;&gt; authorizer = ftpserver.DummyAuthorizer()
+&gt;&gt;&gt; authorizer.add_user("user", "12345", "/home/user", perm="elradfmw")
+&gt;&gt;&gt; authorizer.add_anonymous("/home/nobody")
+&gt;&gt;&gt; handler = ftpserver.FTPHandler
+&gt;&gt;&gt; handler.authorizer = authorizer
+&gt;&gt;&gt; address = ("127.0.0.1", 21)
+&gt;&gt;&gt; ftpd = ftpserver.FTPServer(address, handler)
+&gt;&gt;&gt; ftpd.serve_forever()
+Serving FTP on 127.0.0.1:21
+[]127.0.0.1:2503 connected.
+127.0.0.1:2503 ==&gt; 220 Ready.
+127.0.0.1:2503 &lt;== USER anonymous
+127.0.0.1:2503 ==&gt; 331 Username ok, send password.
+127.0.0.1:2503 &lt;== PASS ******
+127.0.0.1:2503 ==&gt; 230 Login successful.
+[anonymous]@127.0.0.1:2503 User anonymous logged in.
+127.0.0.1:2503 &lt;== TYPE A
+127.0.0.1:2503 ==&gt; 200 Type set to: ASCII.
+127.0.0.1:2503 &lt;== PASV
+127.0.0.1:2503 ==&gt; 227 Entering passive mode (127,0,0,1,9,201).
+127.0.0.1:2503 &lt;== LIST
+127.0.0.1:2503 ==&gt; 150 File status okay. About to open data connection.
+[anonymous]@127.0.0.1:2503 OK LIST "/". Transfer starting.
+127.0.0.1:2503 ==&gt; 226 Transfer complete.
+[anonymous]@127.0.0.1:2503 Transfer complete. 706 bytes transmitted.
+127.0.0.1:2503 &lt;== QUIT
+127.0.0.1:2503 ==&gt; 221 Goodbye.
+[anonymous]@127.0.0.1:2503 Disconnected.</pre><h2><a name="Discussion_group"></a>Discussion group<a href="#Discussion_group" class="section_anchor">¶</a></h2><p><a href="http://groups.google.com/group/pyftpdlib/topics" rel="nofollow">http://groups.google.com/group/pyftpdlib/topics</a> </p><h2><a name="Timeline"></a>Timeline<a href="#Timeline" class="section_anchor">¶</a></h2><ul><li>2010-11-07: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib-0.6.0.tar.gz" rel="nofollow">0.6.0</a> released. </li><li>2010-08-24: pyftpdlib included in <a href="http://www.peerscape.org/" rel="nofollow">peerscape</a> project. </li><li>2010-07-15: pyftpdlib included in <a href="http://tomatohater.com/faetus/" rel="nofollow">Faetus</a> project. </li><li>2010-07-11: pyftpdlib included in <a href="http://code.google.com/p/pyfilesystem" rel="nofollow">Pyfilesystem</a> project. </li><li>2010-06-28: pyftpdlib has been <a href="http://packages.debian.org/sid/python-pyftpdlib" rel="nofollow">packaged for Debian</a> </li><li>2010-04-28: pyftpdlib included in <a href="http://forge.openbravo.com/plugins/mwiki/index.php/MobilePOS" rel="nofollow">sierramodulepos</a> project. </li><li>2010-03-20: <a href="http://www.smartfile.com/" rel="nofollow">http://www.smartfile.com</a> uses pyftpdlib. </li><li>2010-01-13: pyftpdlib included in <a href="http://code.irondojo.com/" rel="nofollow">zenftp</a> project. </li><li>2009-12-26: pyftpdlib included in <a href="http://code.google.com/p/sypftp" rel="nofollow">Symbian Python FTP server</a> project. </li><li>2009-11-04: <a href="http://www.netplay.it/" rel="nofollow">www.netplay.it</a> uses pyftpdlib. </li><li>2009-11-04: <a href="http://www.adcast.tv/" rel="nofollow">www.adcast.tv</a> uses pyftpdlib. </li><li>2009-11-04: <a href="http://www.bitsontherun.com/" rel="nofollow">www.bitsontherun.com</a> uses pyftpdlib. </li><li>2009-11-02: pyftpdlib included in <a href="http://github.com/chmouel/ftp-cloudfs" rel="nofollow">ftp-cloudfs</a> project. </li><li>2009-09-14: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib-0.5.2.tar.gz" rel="nofollow">0.5.2</a> released. </li><li>2009-08-10: pyftpdlib included in <a href="http://github.com/wuzhe/imgserve/tree/master" rel="nofollow">Imgserve</a> project. </li><li>2009-07-22: pyftpdlib included in <a href="http://plumi.org/wiki" rel="nofollow">Plumi</a> project. </li><li>2009-04-02: pyftpdlib RPM-packaged and ported on <a href="https://admin.fedoraproject.org/pkgdb/packages/name/pyftpdlib" rel="nofollow">Fedora</a> to make users can easily install on it via <i>yum install pyftpdlib</i>. </li><li>2009-03-28: pyftpdlib included in <a href="http://bazaar-vcs.org/" rel="nofollow">Bazaar</a> project. </li><li>2009-02-23: pyftpdlib included in <a href="http://git.logfish.net/shareftp.git/" rel="nofollow">ShareFTP</a> project. </li><li>2009-01-21: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib-0.5.1.tar.gz" rel="nofollow">0.5.1</a> released. </li><li>2008-12-27: pyftpdlib included in <a href="http://code.google.com/intl/it-IT/chromium/" rel="nofollow">Google Chromium</a>, the open source project behind <a href="http://www.google.com/chrome" rel="nofollow">Google Chrome</a>. </li><li>2008-12-27: pyftpdlib ported on <a href="http://www.gnu-darwin.org/" rel="nofollow">GNU Darwin</a> systems to make users can easily install on it. </li><li>2008-11-26: pyftpdlib included in <a href="http://openerp.com/" rel="nofollow">OpenERP</a>. </li><li>2008-10-26: pyftpdlib included in <a href="http://www.vmspython.org/" rel="nofollow">Python for OpenVMS</a> as standard package. </li><li>2008-10-09: pyftpdlib included in <a href="http://bbs.archlinux.org/viewtopic.php?pid=431474" rel="nofollow">Shareme</a> project. </li><li>2008-09-20: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib-0.5.0.tar.gz" rel="nofollow">0.5.0</a> released. </li><li>2008-08-10: pyftpdlib included in <a href="http://trac.manent-backup.com/" rel="nofollow">Manent</a> project. </li><li>2008-05-16: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib-0.4.0.tar.gz" rel="nofollow">0.4.0</a> released. </li><li>2008-04-09: pyftpdlib used as backend for <a href="http://arkadiusz-wahlig.blogspot.com/2008/04/hosting-files-on-google.html" rel="nofollow">gpftpd</a>, an FTP server for managing files hosted on <a href="http://pages.google.com/" rel="nofollow">Google Pages</a>. </li><li>2008-01-17: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib-0.3.0.tar.gz" rel="nofollow">0.3.0</a> released. </li><li>2007-10-14: pyftpdlib included in <a href="http://walco.n--tree.net/projects/aksy/wiki" rel="nofollow">Aksy</a> project. </li><li>2007-09-17: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib_0.2.0.tar.gz" rel="nofollow">0.2.0</a> released. </li><li>2007-09-08: pyftpdlib included as <a href="http://farmanager.com/" rel="nofollow">FarManager</a> <a href="http://www.farmanager.com/enforum/viewtopic.php?t=640&amp;highlight=&amp;sid=12d4d90f27f421243bcf7a0e3c516efb" rel="nofollow">plug-in</a>. </li><li>2007-03-06: pyftpdlib <a href="http://www.freshports.org/ftp/py-pyftpdlib/" rel="nofollow">ported on FreeBSD</a> systems to make users can easily install on it. </li><li>2007-03-07: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib_0.1.1.tar.gz" rel="nofollow">0.1.1</a> released. </li><li>2007-02-26: version <a href="http://pyftpdlib.googlecode.com/files/pyftpdlib_0.1.tar.gz" rel="nofollow">0.1.0</a> released. </li></ul><h2><a name="Contribute"></a>Contribute<a href="#Contribute" class="section_anchor">¶</a></h2><p>If you want to help or just give us suggestions about the project and other related things, subscribe to the <a href="http://groups.google.com/group/pyftpdlib" rel="nofollow">discussion mailing list</a>.
+ If you want to talk with project team members about pyftpdlib and other
+ related things feel free to contact us at the following addresses: </p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Name</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Country</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>E-mail</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Giampaolo Rodola' </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Turin (Italy) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> g.rodola at gmail dot com </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Original pyftpdlib author and main maintainer. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Jay Loden </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> New Jersey (USA) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> jloden at gmail dot com </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> OS X and Linux platform development/testing </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Janos Guljas </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> janos at janos.in dot rs </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Debian package maintainer </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Silas Sewell </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Denver (USA) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> silas at sewell dot ch </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Fedora package maintainer </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Li-Wen Hsu </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Taiwan </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> lwhsu at freebsd dot org </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> FreeBSD package maintainer </td></tr> </tbody></table></p><p>Feedbacks and suggestions are greatly appreciated as well as new testers and coders willing to join the development.<br> For any bug report, patch proposal or feature request, add an entry into the <a href="http://code.google.com/p/pyftpdlib/issues/list" rel="nofollow">Issue Tracker</a>.<br> In case you're using pyftpdlib into a software or website of yours, please update the pyftpdlib <a href="http://code.google.com/p/pyftpdlib/wiki/Adoptions" rel="nofollow">Adoptions List</a> by adding a comment in the Wiki. </p><p>Thank you. </p><h2><a name="Statistics"></a>Statistics<a href="#Statistics" class="section_anchor">¶</a></h2><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+<script src="index_files/rpc.js"></script>
+<script type="text/javascript">
+function resizeIframeHandler(opt_height) {
+ var elem = document.getElementById(this.f);
+ if (!elem) return;
+ if (!opt_height) {
+ elem.style.height = undefined;
+ }
+ else {
+ opt_height = Math.max(10, opt_height);
+ elem.style.height = opt_height + 'px';
+ }
+}
+gadgets.rpc.register("resize_iframe", resizeIframeHandler);
+
+gadgets.rpc.register('set_title', function(title) {
+ var elem = document.getElementById(this.f + '_title');
+ if (elem) {
+ elem.innerHTML = gadgets.util.escape(title);
+ }
+});
+</script>
+<h2 id="gadget0_title" class="gadget-title"></h2><iframe src="index_files/ifr.html" id="gadget0" name="gadget0" frameborder="0" height="200" width="340"></iframe> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <h2 id="gadget1_title" class="gadget-title"></h2><iframe src="index_files/ifr_002.html" id="gadget1" name="gadget1" frameborder="0" height="220"></iframe> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <h2 id="gadget2_title" class="gadget-title"></h2><iframe src="index_files/ifr_003.html" id="gadget2" name="gadget2" frameborder="0" height="250"></iframe> </td></tr> </tbody></table></p><h2><a name="Trademarks"></a>Trademarks<a href="#Trademarks" class="section_anchor">¶</a></h2><p>Some famous trademarks which adopted pyftpdlib (<a href="http://code.google.com/p/pyftpdlib/wiki/Adoptions" rel="nofollow">complete list</a>). </p><p><p></p><p></p><p></p><p></p><p></p><table> <tbody><tr><td valign="bottom"> <a href="http://www.google.com/chrome" title="Google Chrome" rel="nofollow"> <img src="index_files/logo_sm.jpg" alt="Google Chrome"> </a> <br><br><br><br> </td> <td valign="bottom"> <a href="http://www.debian.org/" title="Linux Debian" rel="nofollow"> <img src="index_files/100px_debian_logo.png" alt="Linux Debian"> </a> </td> <td valign="bottom"> <a href="http://fedoraproject.org/" title="Linux Fedora" rel="nofollow"> <img src="index_files/fedora-logo.png" alt="Linux Fedora"> </a> </td> <td valign="bottom"> <a href="http://www.freebsd.org/" title="Free BSD" rel="nofollow"> <img src="index_files/beastie.gif" alt="Free BSD"> </a> </td> <td valign="bottom"> <a href="http://openerp.com/" title="Open ERP" rel="nofollow"> <blockquote><img src="index_files/openlogo.png" alt="Open ERP">
+</blockquote></a> </td> </tr></tbody></table> </p><p><p></p><p></p><p></p><p></p><p></p><table> <tbody><tr><td valign="top"> <br><br> <a href="http://bazaar-vcs.org/" title="Bazaar control revision system" rel="nofollow"> <img src="index_files/bazaar-logo.jpg" alt="Bazaar control revision system"> </a> </td> <td valign="top"> <a href="http://www.bitsontherun.com/" title="Bits On The Run" rel="nofollow"> <blockquote><img src="index_files/bits-on-the-run-logo-200x411.xml" alt="Bits On The Run">
+</blockquote></a> </td> <td> <a href="http://www.bitsontherun.com/" title="Python for OpenVMS" rel="nofollow"> <img src="index_files/wwwovmsorg_logo.png" alt="Python for OpenVMS"> </a> </td> <td> <a href="http://www.smartfile.com/" title="Smartfile" rel="nofollow"> <blockquote><img src="index_files/logo.gif" alt="Smartfile">
+</blockquote></a> </td> </tr></tbody></table> </p>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+
+
+
+
+<script type="text/javascript" src="index_files/dit_scripts.js"></script>
+
+
+
+ </body></html>
diff --git a/chromium/third_party/pyftpdlib/src/doc/install.html b/chromium/third_party/pyftpdlib/src/doc/install.html
new file mode 100644
index 00000000000..d60cec7820a
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/install.html
@@ -0,0 +1,51 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style>
+.section_anchor {
+ font-size:0px;
+}
+</style>
+
+
+
+
+
+ <title>Install</title>
+ </head><body>
+
+
+
+
+ <div id="wikicontent">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+
+ <td class="vt" id="wikimaincol" width="100%">
+
+ <div id="wikiheader" style="margin-bottom: 1em;">
+
+ &nbsp;
+ <span style="font-size: 120%; font-weight: bold;">Install</span>
+ &nbsp;
+
+
+ <div style="font-style: italic; margin-top: 3px;">Instructions for installing pyftpdlib (for those really new to Python).</div>
+
+ </div>
+ <h1><a name="Requirements"></a>Requirements<a href="#Requirements" class="section_anchor">¶</a></h1><p>Python 2.3 or higher (not Python 3.x). You can get the latest 2.x stable Python release from here: <a href="http://www.python.org/download/" rel="nofollow">http://www.python.org/download/</a>. </p><h1><a name="Using_easy_install_/_setuptools"></a>Using easy_install / setuptools<a href="#Using_easy_install_/_setuptools" class="section_anchor">¶</a></h1><p>If you have <a href="http://pypi.python.org/pypi/setuptools" rel="nofollow">easy_install / setuptools</a> on your system, installing pyftpdlib is quite simple. Just run: </p><pre class="prettyprint">easy_install pyftpdlib</pre><p>This will get the most updated pyftpdlib from the Python <a href="http://pypi.python.org/pypi" rel="nofollow">pypi repository</a>, unpack it and install it automatically. </p><p>Note:
+ if you already have an old version of pyftpdlib installed, easy_install
+ will not automatically download the latest version. You can ask for a
+particular version by running, for example: </p><pre class="prettyprint">easy_install.py pyftpdlib==0.3.0</pre><h1><a name="Manual_installation"></a>Manual installation<a href="#Manual_installation" class="section_anchor">¶</a></h1><p>If you have downloaded a pyftpdlib package, follow the following steps: </p><p>Unpack it (Windows users could use 7Zip, WinRar or other similar program): </p><pre class="prettyprint">tar zxvf pyftpdlib-0.3.0.tar.gz</pre><p>Change to the pyftpdlib directory: </p><pre class="prettyprint">cd pyftpdlib</pre><p>Run setup.py to install pyftpdlib. This step need to be run as root. </p><pre class="prettyprint">python setup.py install</pre><p>If you're on Windows just run: </p><pre class="prettyprint">setup.py install</pre>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+
+
+
+
+<script type="text/javascript" src="install_files/dit_scripts.js"></script>
+
+
+
+ </body></html>
diff --git a/chromium/third_party/pyftpdlib/src/doc/logo.png b/chromium/third_party/pyftpdlib/src/doc/logo.png
new file mode 100644
index 00000000000..f4943d7baff
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/logo.png
Binary files differ
diff --git a/chromium/third_party/pyftpdlib/src/doc/release-notes.html b/chromium/third_party/pyftpdlib/src/doc/release-notes.html
new file mode 100644
index 00000000000..e51759057f6
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/release-notes.html
@@ -0,0 +1,63 @@
+
+<!-- saved from url=(0067)http://code.google.com/p/pyftpdlib/wiki/ReleaseNotes06?show=content -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>ReleaseNotes06</title>
+ </head>
+ <body>
+<style>
+.section_anchor {
+ font-size:0px;
+}
+</style>
+
+
+
+
+ <div id="wikicontent">
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tbody><tr>
+
+ <td class="vt" id="wikimaincol" width="100%">
+
+ <div id="wikiheader" style="margin-bottom:1em">
+
+ <img width="15" height="15" id="star_img" src="./release-notes_files/star_off.gif" style="cursor:pointer" onclick="_CS_toggleStar(this,
+ &#39;&#39;,
+ {&#39;scope&#39;: &#39;wiki&#39;,
+ &#39;user&#39;: &#39;_CURRENT_USER&#39;,
+ &#39;item&#39;: &#39;pyftpdlib:ReleaseNotes06&#39;,
+ &#39;token&#39;: codesite_token
+ });">
+
+ &nbsp;
+ <span style="font-size:120%;font-weight:bold">ReleaseNotes06</span>
+ &nbsp;
+
+
+ <div style="font-style:italic; margin-top: 3px">pyftpdlib 0.6.x series release notes.</div>
+
+ </div>
+ <h1><a name="Version:_0.6.0_-_Date:_2010-01-24"></a>Version: 0.6.0 - Date: 2010-01-24<a href="http://code.google.com/p/pyftpdlib/wiki/ReleaseNotes06?show=content#Version:_0.6.0_-_Date:_2010-01-24" class="section_anchor">¶</a></h1><h2><a name="Enhancements"></a>Enhancements<a href="http://code.google.com/p/pyftpdlib/wiki/ReleaseNotes06?show=content#Enhancements" class="section_anchor">¶</a></h2><ul><li><a title="Add SSL/TLS support to pyftpdlib [RFCs 2228 &amp; 4217]" href="http://code.google.com/p/pyftpdlib/issues/detail?id=68">Issue 68</a>: added full FTPS (FTP over SSL/TLS) support. </li><li><a title="MDTM should return time in GMT" href="http://code.google.com/p/pyftpdlib/issues/detail?id=86">Issue 86</a>: pyftpdlib now reports all ls and MDTM timestamps as GMT times, as recommended in RFC-3659. A <tt>FTPHandler.use_gmt_times</tt> attributed has been added and can be set to False in case local times are desired instead. </li><li><a title="Provide a command line parser to configure the FTP server when run with python&#39;s -m option" href="http://code.google.com/p/pyftpdlib/issues/detail?id=124">Issue 124</a>: pyftpdlib now accepts command line options to configure a stand alone anonymous FTP server when running pyftpdlib with python's -m option. </li><li><a title="ECONNREFUSED when using masquerade_address with multiple listening IPs." href="http://code.google.com/p/pyftpdlib/issues/detail?id=127">Issue 127</a>: <tt>added FTPHandler.masquerade_address_map</tt> option which allows you to define multiple 1 to 1 mappings in case you run a FTP server with multiple private IP addresses behind a NAT firewall with multiple public IP addresses. </li><li><a title="user name and group name should be resolved via AbstractedFS." href="http://code.google.com/p/pyftpdlib/issues/detail?id=128">Issue 128</a>: files and directories owner and group names and <tt>os.readlink</tt> are now resolved via <tt>AbstractedFS</tt> methods instead of in <tt>format_list()</tt>. </li><li><a title="Callbacks for incomplete file transfers and user login" href="http://code.google.com/p/pyftpdlib/issues/detail?id=129">Issue 129</a>: added 3 new callbacks to <tt>FTPHandler</tt> class: <tt>on_incomplete_file_sent()</tt>, <tt>on_incomplete_file_received()</tt> and <tt>on_login()</tt>. </li><li><a title="Move Unix and Windows authorizers from demo directory to pyftpdlib/contrib" href="http://code.google.com/p/pyftpdlib/issues/detail?id=130">Issue 130</a>: added <tt>UnixAuthorizer</tt> and <tt>WindowsAuthorizer</tt> classes defined in the new <tt>pyftpdlib.contrib.authorizers</tt> module. </li><li><a title="Support both IPv4 and IPv6 by using a single socket" href="http://code.google.com/p/pyftpdlib/issues/detail?id=131">Issue 131</a>: pyftpdlib is now able to serve both IPv4 and IPv6 at the same time by using a single socket. </li><li><a title="AbstractedFS changes" href="http://code.google.com/p/pyftpdlib/issues/detail?id=133">Issue 133</a>: <tt>AbstractedFS</tt> constructor now accepts two argumets: <tt>root</tt> and <tt>cmd_channel</tt> breaking compatibility with previous version. Also, <tt>root</tt> and <tt>cwd</tt> attributes became properties. The previous bug consisting in re-setting the root from the ftp handler after user login has been fixed to ease the development of subclasses. </li><li><a title="Enable TCP_NODELAY socket option" href="http://code.google.com/p/pyftpdlib/issues/detail?id=134">Issue 134</a>: enabled TCP_NODELAY socket option for the FTP command channels resulting in pyftpdlib being twice faster. </li><li><a title="Remove python 2.3 support" href="http://code.google.com/p/pyftpdlib/issues/detail?id=135">Issue 135</a>: Python 2.3 support has been removed. </li><li><a title="Implement UnixFilesystem class" href="http://code.google.com/p/pyftpdlib/issues/detail?id=137">Issue 137</a>: added new <tt>pyftpdlib.contrib.filesystems</tt> module within <tt>UnixFilesystem</tt> class which permits the client to escape its home directory and navigate the real filesystem. </li><li><a title="Calculate data transfer elapsed time" href="http://code.google.com/p/pyftpdlib/issues/detail?id=138">Issue 138</a>: added <tt>DTPHandler.get_elapsed_time()</tt> method which returns the transfer elapsed time in seconds. </li></ul><h2><a name="Bugfixes"></a>Bugfixes<a href="http://code.google.com/p/pyftpdlib/wiki/ReleaseNotes06?show=content#Bugfixes" class="section_anchor">¶</a></h2><ul><li><a title="race condition on PORT connections" href="http://code.google.com/p/pyftpdlib/issues/detail?id=120">Issue 120</a>: an <tt>ActiveDTP</tt> instance is not garbage collected in case a client issuing PORT disconnects before establishing the data connection. </li><li><a title="a bug in verifying path" href="http://code.google.com/p/pyftpdlib/issues/detail?id=122">Issue 122</a>: a wrong variable name was used in <tt>AbstractedFS.validpath</tt> method. </li><li><a title="PORT commands fail to bind to command channel ip address" href="http://code.google.com/p/pyftpdlib/issues/detail?id=123">Issue 123</a>: PORT command doesn't bind to correct address in case an alias is created for the local network interface. </li><li><a title="PWD response should escape double quotes" href="http://code.google.com/p/pyftpdlib/issues/detail?id=140">Issue 140</a>: pathnames returned in PWD response should have double-quotes '"' escaped. </li></ul><h2><a name="API_changes_since_0.5.2"></a>API changes since 0.5.2<a href="http://code.google.com/p/pyftpdlib/wiki/ReleaseNotes06?show=content#API_changes_since_0.5.2" class="section_anchor">¶</a></h2><ul><li>removed support for Python 2.3. </li><li>all classes are now new-style classes. </li><li>Added a new package in pyftpdlib namespace: "contrib". Modules (and classes) defined here: </li><ul><li><tt>pyftpdlib.contrib.handlers.py</tt> (<tt>TLS_FTPHandler</tt>, <tt>TLS_FTPHandlerFactory</tt>) </li><li><tt>pyftpdlib.contrib.authorizers.py</tt> (<tt>UnixAuthorizer</tt>, <tt>WindowsAuthorizer</tt>) </li><li><tt>pyftpdlib.contrib.filesystems</tt> (<tt>UnixFilesystem</tt>) </li></ul><li><tt>AbstractedFS</tt> class: </li><ul><li><tt>__init__</tt> method now accepts two arguments: <tt>root</tt> and <tt>cmd_channel</tt>. </li><li><tt>root</tt> and <tt>cwd</tt> attributes are now read-only properties. </li><li>3 new methods have been added: </li><ul><li><tt>get_user_by_uid()</tt> </li><li><tt>get_group_by_gid()</tt> </li><li><tt>readlink()</tt> </li></ul></ul><li><tt>FTPHandler</tt> class: </li><ul><li>new class attributes: </li><ul><li><tt>use_gmt_times</tt> </li><li><tt>tcp_no_delay</tt> </li><li><tt>masquerade_address_map</tt> </li></ul><li>new methods: </li><ul><li><tt>on_incomplete_file_sent()</tt> </li><li><tt>on_incomplete_file_received()</tt> </li><li><tt>on_login()</tt> </li></ul><li><tt>proto_cmds</tt> class attribute has been added. The <tt>FTPHandler</tt> class no longer relies on <tt>ftpserver.proto_cmds</tt> global dictionary but on <tt>ftpserver.FTPHandler.proto_cmds</tt> instead. </li></ul><li><tt>FTPServer</tt> class: </li><ul><li><tt>max_cons</tt> attribute defaults to 512 by default instead of 0 (unlimited). </li></ul><li><tt>DummyAuthorizer</tt> class: </li><ul><li><tt>ValueError</tt> exceptions are now raised instead of <tt>AuthorizerError</tt> </li></ul><li><tt>DTPHandler</tt> class: </li><ul><li><tt>get_elapsed_time()</tt> method has been added. </li></ul></ul><h2><a name="Migration_notes"></a>Migration notes<a href="http://code.google.com/p/pyftpdlib/wiki/ReleaseNotes06?show=content#Migration_notes" class="section_anchor">¶</a></h2><p>Some of the changes introduced in 0.6.0 break compatibility with previous 0.5.x serie. In particular you should be careful in case you're using a customized file system class defining an <tt>__init__</tt> method since <tt>AbstractedFS</tt> constructor now expects two arguments. </p>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+
+
+
+
+<script type="text/javascript" src="./release-notes_files/dit_scripts.js"></script>
+
+
+ <script type="text/javascript">
+ _fetchOptions(
+ "", "pyftpdlib", "wikiOptions",
+ codesite_token, 1240514232);
+ _onload();
+ </script>
+
+
+
+
+
+</body></html>
diff --git a/chromium/third_party/pyftpdlib/src/doc/rfcs-compliance.html b/chromium/third_party/pyftpdlib/src/doc/rfcs-compliance.html
new file mode 100644
index 00000000000..a8da3f6551c
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/rfcs-compliance.html
@@ -0,0 +1,84 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style>
+.section_anchor {
+ font-size:0px;
+}
+</style>
+
+
+
+
+ <title>RFCsCompliance</title>
+ </head><body>
+
+
+
+
+ <div id="wikicontent">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+
+ <td class="vt" id="wikimaincol" width="100%">
+
+ <div id="wikiheader" style="margin-bottom: 1em;">
+
+ &nbsp;
+ <span style="font-size: 120%; font-weight: bold;">RFCsCompliance</span>
+ &nbsp;
+
+
+ <div style="font-style: italic; margin-top: 3px;">A paper showing pyftpdlib compliance against FTP protocol standard RFCs.</div>
+
+ </div>
+ <h1><a name="Table_of_contents"></a>Table of contents<a href="#Table_of_contents" class="section_anchor">¶</a></h1><p></p><ul><li><a href="#Table_of_contents">Table of contents</a></li><li><a href="#Introduction">Introduction</a></li><li><a href="#RFC-959_-_File_Transfer_Protocol">RFC-959 - File Transfer Protocol</a></li><li><a href="#RFC-1123_-_Requirements_for_Internet_Hosts">RFC-1123 - Requirements for Internet Hosts</a></li><li><a href="#RFC-2228_-_FTP_Security_Extensions">RFC-2228 - FTP Security Extensions</a></li><li><a href="#RFC-2389_-_Feature_negotiation_mechanism_for_the_File_Transfer_P">RFC-2389 - Feature negotiation mechanism for the File Transfer Protocol</a></li><li><a href="#RFC-2428_-_FTP_Extensions_for_IPv6_and_NATs">RFC-2428 - FTP Extensions for IPv6 and NATs</a></li><li><a href="#RFC-2577_-_FTP_Security_Considerations">RFC-2577 - FTP Security Considerations</a></li><li><a href="#RFC-2640_-_Internationalization_of_the_File_Transfer_Protocol">RFC-2640 - Internationalization of the File Transfer Protocol</a></li><li><a href="#RFC-3659_-_Extensions_to_FTP">RFC-3659 - Extensions to FTP</a></li><li><a href="#RFC-4217_-_Securing_FTP_with_TLS">RFC-4217 - Securing FTP with TLS</a></li></ul> <p></p><h1><a name="Introduction"></a>Introduction<a href="#Introduction" class="section_anchor">¶</a></h1><p>This page lists current standard Internet RFCs that define the FTP protocol. </p><p>pyftpdlib conforms to the FTP protocol standard as defined in <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a> and <a href="http://www.ietf.org/rfc/rfc1123.txt" rel="nofollow">RFC-1123</a>
+ implementing all the fundamental commands and features described in
+them. It also implements some more recent features such as OPTS and FEAT
+ commands (<a href="http://www.ietf.org/rfc/rfc2389.txt" rel="nofollow">RFC-2398</a>), EPRT and EPSV commands covering the IPv6 support (<a href="ftp://ftp.rfc-editor.org/in-notes/rfc2428.txt" rel="nofollow">RFC-2428</a>) and MDTM, MLSD, MLST and SIZE commands defined in <a href="http://www.ietf.org/rfc/rfc3659.txt" rel="nofollow">RFC-3659</a>. </p><p>Future plans for pyftpdlib include the gradual implementation of other standards track RFCs. </p><p>Some
+ of the features like ACCT or SMNT commands will never be implemented
+deliberately. Other features described in more recent RFCs like the
+TLS/SSL support for securing FTP (<a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a>) are now implemented as a <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/demo/tls_ftpd.py" rel="nofollow">demo script</a>, waiting to reach the proper level of stability to be then included in the standard code base. </p><h1><a name="RFC-959_-_File_Transfer_Protocol"></a>RFC-959 - File Transfer Protocol<a href="#RFC-959_-_File_Transfer_Protocol" class="section_anchor">¶</a></h1><p>The base specification of the current File Transfer Protocol. </p><ul><li>Issued: October 1985 </li><li>Status: STANDARD </li><li>Obsoletes: <a href="http://www.ietf.org/rfc/rfc765.txt" rel="nofollow">RFC-765</a> </li><li>Updated by: <a href="http://www.ietf.org/rfc/rfc1123.txt" rel="nofollow">RFC-1123</a>, <a href="http://www.ietf.org/rfc/rfc2228.txt" rel="nofollow">RFC-2228</a>, <a href="http://www.ietf.org/rfc/rfc2640.txt" rel="nofollow">RFC-2640</a>, <a href="http://www.ietf.org/rfc/rfc2773.txt" rel="nofollow">RFC-2773</a> </li><li><a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Command</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> ABOR </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Abort data transfer. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> ACCT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Specify account information. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> It will never be implemented (useless). </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> ALLO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Ask for server to allocate enough storage space. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Treated as a NOOP (no operation). </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> APPE </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Append data to an existing file. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> CDUP </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Go to parent directory. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> CWD </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Change current working directory. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> DELE </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Delete file. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> HELP </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Show help. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Accept also arguments. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> LIST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> List files. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Accept also bad arguments like "-ls", "-la", ... </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MKD </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Create directory. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MODE </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set data transfer mode. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> "STREAM" mode is supported, "Block" and "Compressed" aren't. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NLST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> List files in a compact form. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NOOP </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NOOP (no operation), just do nothing. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PASS </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set user password. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PASV </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set server in passive connection mode. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PORT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set server in active connection mode. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PWD </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Get current working directory. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> QUIT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Quit session. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> If file transfer is in progress, the connection will remain open until it is finished. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> REIN </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Reinitialize user's current session. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> REST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Restart file position. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> RETR </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Retrieve a file (client's download). </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> RMD </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Remove directory. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> RNFR </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> File renaming (source) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> RNTO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> File renaming (destination) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> SITE </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.5.1 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Site specific server services. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ No SITE commands aside from "SITE HELP" are implemented by default.
+The user willing to add support for a specific SITE command has to
+define a new <tt>ftp_SITE_%CMD%</tt> method in the <tt>FTPHandler</tt> subclass. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> SMNT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Mount file-system structure. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Will never be implemented (too much system-dependent and almost never used). </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> STAT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Server's status information / File LIST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> STOR </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Store a file (client's upload). </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> STOU </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Store a file with a unique name. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> STRU </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set file structure. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Supports only File type structure by doing a NOOP (no operation). Other structure types (Record and Page) are not implemented. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> SYST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Get system type. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Always return "UNIX Type: L8" because of the LIST output provided. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> TYPE </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set current type (Binary/ASCII). </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Accept only Binary and ASII TYPEs. Other TYPEs such as EBCDIC are obsoleted, system-dependent and thus not implemented. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> USER </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set user. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ A new USER command could be entered at any point in order to change the
+ access control flushing any user, password, and account information
+already supplied and beginning the login sequence again. </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-1123_-_Requirements_for_Internet_Hosts"></a>RFC-1123 - Requirements for Internet Hosts<a href="#RFC-1123_-_Requirements_for_Internet_Hosts" class="section_anchor">¶</a></h1><p>Extends and clarifies some aspects of <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a>. Introduces new response codes 554 and 555. </p><ul><li>Issued: October 1989 </li><li>Status: STANDARD </li><li><a href="http://www.ietf.org/rfc/rfc1123.txt" rel="nofollow">Link</a> </li></ul><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Feature</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> TYPE L 8 as synonym of TYPE I </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> TYPE L 8 command should be treated as synonym of TYPE I ("IMAGE" or binary type). </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PASV is per-transfer </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PASV must be used for a unique transfer. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> If PASV is issued twice data-channel is restarted. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Implied type for LIST and NLST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> The data returned by a LIST or NLST command SHOULD use an implied TYPE AN. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> STOU format output </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Defined the exact format output which STOU response must respect ("125/150 FILE filename"). </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Avoid 250 response type on STOU </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> The 250 positive response indicated in <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a> has been declared incorrect in <a href="http://www.ietf.org/rfc/rfc1123.txt" rel="nofollow">RFC-1123</a> which requires 125/150 instead. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Handle "Experimental" directory cmds </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ The server should support XCUP, XCWD, XMKD, XPWD and XRMD obsoleted
+commands and treat them as synonyms for CDUP, CWD, MKD, LIST and RMD
+commands. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Idle timeout </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.5.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ A Server-FTP process SHOULD have a configurable idle timeout of 5
+minutes, which will terminate the process and close the control
+connection if the server is inactive (i.e., no command or data transfer
+in progress) for a long period of time. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Concurrency of data and control </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Server-FTP should be able to process STAT or ABOR while a data transfer is in progress </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Feature granted natively for ALL commands since we're in an asynchronous environment. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 554 response on wrong REST </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ Return a 554 reply may for a command that follows a REST command. The
+reply indicates that the existing file at the Server-FTP cannot be
+repositioned as specified in the REST. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-2228_-_FTP_Security_Extensions"></a>RFC-2228 - FTP Security Extensions<a href="#RFC-2228_-_FTP_Security_Extensions" class="section_anchor">¶</a></h1><p>Specifies several security extensions to the base FTP protocol defined in <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a>.
+ New commands: AUTH, ADAT, PROT, PBSZ, CCC, MIC, CONF, and ENC. New
+response codes: 232, 234, 235, 334, 335, 336, 431, 533, 534, 535, 536,
+537, 631, 632, and 633. </p><ul><li>Issued: October 1997 </li><li>Status: PROPOSED STANDARD </li><li>Updates: <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a> </li><li><a href="http://www.ietf.org/rfc/rfc2228.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Command</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> AUTH </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Authentication/Security Mechanism. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Implemented as <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/demo/tls_ftpd.py" rel="nofollow">demo script</a> by following the <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a> guide line. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> CCC </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Clear Command Channel. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> CONF </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Confidentiality Protected Command. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Somewhat obsoleted by <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a>. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> EENC </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Privacy Protected Command. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Somewhat obsoleted by <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a>. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MIC </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Integrity Protected Command. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Somewhat obsoleted by <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a>. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PBSZ </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Protection Buffer Size. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Implemented as <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/demo/tls_ftpd.py" rel="nofollow">demo script</a> by following the <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a> guide line as a no-op command. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PROT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Data Channel Protection Level. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Implemented as <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/demo/tls_ftpd.py" rel="nofollow">demo script</a> by following the <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a> guide line supporting only "P" and "C" protection levels. </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-2389_-_Feature_negotiation_mechanism_for_the_File_Transfer_P"></a>RFC-2389 - Feature negotiation mechanism for the File Transfer Protocol<a href="#RFC-2389_-_Feature_negotiation_mechanism_for_the_File_Transfer_P" class="section_anchor">¶</a></h1><p>Introduces the new FEAT and OPTS commands. </p><ul><li>Issued: August 1998 </li><li>Status: PROPOSED STANDARD </li><li><a href="http://www.ietf.org/rfc/rfc2389.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Command</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> FEAT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.3.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> List new supported commands subsequent <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> OPTS </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.3.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set options for certain commands. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MLST is the only command which could be used with OPTS. </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-2428_-_FTP_Extensions_for_IPv6_and_NATs"></a>RFC-2428 - FTP Extensions for IPv6 and NATs<a href="#RFC-2428_-_FTP_Extensions_for_IPv6_and_NATs" class="section_anchor">¶</a></h1><p>Introduces
+ the new commands EPRT and EPSV extending FTP to enable its use over
+various network protocols, and the new response codes 522 and 229. </p><ul><li>Issued: September 1998 </li><li>Status: PROPOSED STANDARD </li><li><a href="http://www.ietf.org/rfc/rfc2428.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Command</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> EPRT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.4.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set active data connection over IPv4 or IPv6 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> EPSV </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.4.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set passive data connection over IPv4 or IPv6 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-2577_-_FTP_Security_Considerations"></a>RFC-2577 - FTP Security Considerations<a href="#RFC-2577_-_FTP_Security_Considerations" class="section_anchor">¶</a></h1><p>Provides
+ several configuration and implementation suggestions to mitigate some
+security concerns, including limiting failed password attempts and
+third-party "proxy FTP" transfers, which can be used in "bounce
+attacks". </p><ul><li>Issued: May 1999 </li><li>Status: INFORMATIONAL </li><li><a href="http://www.ietf.org/rfc/rfc2577.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Feature</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> FTP bounce protection </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ Reject PORT if IP address specified in it does not match client IP
+address. Drop the incoming (PASV) data connection for the same reason. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Configurable. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Restrict PASV/PORT to non privileged ports </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Reject connections to privileged ports. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Configurable. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Brute force protection (1) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Disconnect client after a certain number (3 or 5) of wrong authentications. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Configurable. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Brute force protection (2) </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.5.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Impose a 5 second delay before replying to an invalid "PASS" command to diminish the efficiency of a brute force attack. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Per-source-IP limit </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.2.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Limit the total number of per-ip control connections to avoid parallel brute-force attack attempts. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Configurable. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Do not reject wrong usernames </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Always return 331 to the USER command to prevent client from determining valid usernames on the server. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Port stealing protection </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.1 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Use random-assigned local ports for data connections. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-2640_-_Internationalization_of_the_File_Transfer_Protocol"></a>RFC-2640 - Internationalization of the File Transfer Protocol<a href="#RFC-2640_-_Internationalization_of_the_File_Transfer_Protocol" class="section_anchor">¶</a></h1><p>Extends
+ the FTP protocol to support multiple character sets, in addition to the
+ original 7-bit ASCII. Introduces the new LANG command. </p><ul><li>Issued: July 1999 </li><li>Status: PROPOSED STANDARD </li><li>Updates: <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a> </li><li><a href="http://www.ietf.org/rfc/rfc2640.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Feature</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> LANG command </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Set current response's language. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Support for UNICODE </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> For support of global compatibility it is rencommended that clients and servers use UTF-8 encoding when exchanging pathnames. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-3659_-_Extensions_to_FTP"></a>RFC-3659 - Extensions to FTP<a href="#RFC-3659_-_Extensions_to_FTP" class="section_anchor">¶</a></h1><p>Four new commands are added: "SIZE", "MDTM", "MLST", and "MLSD". The existing command "REST" is modified. </p><ul><li>Issued: March 2007 </li><li>Status: PROPOSED STANDARD </li><li>Updates: <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a> </li><li><a href="http://www.ietf.org/rfc/rfc3659.txt" rel="nofollow">Link</a> </li></ul><p></p><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Feature</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MDTM command </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Get file's last modification time </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MLSD command </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.3.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Get directory list in a standardized form. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> MLST command </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.3.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Get file information in a standardized form. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> SIZE command </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Get file size. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> In case of ASCII TYPE it does not perform the ASCII conversion to avoid DoS conditions (see FAQs for more details). </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> TVSF mechanism </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.1.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;">
+ Provide a file system naming conventions modeled loosely upon those of
+the Unix file system supporting relative and absolute path names. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Minimum required set of MLST facts </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.3.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> If conceivably possible, support at least the type, perm, size, unique, and modify MLSX command facts. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> GMT should be used for timestamps </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> 0.6.0 </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> All times reported by MDTM, LIST, MLSD and MLST commands must be in GMT times </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Possibility to change time display between GMT and local time provided as <tt>FTPHandler.use_gmt_times</tt> attribute </td></tr> </tbody></table></p><p><br> </p><h1><a name="RFC-4217_-_Securing_FTP_with_TLS"></a>RFC-4217 - Securing FTP with TLS<a href="#RFC-4217_-_Securing_FTP_with_TLS" class="section_anchor">¶</a></h1><p>Provides a description on how to implement TLS as a security mechanism to secure FTP clients and/or servers. </p><ul><li>Issued: October 2005 </li><li>Status: STANDARD </li><li>Updates: <a href="http://www.ietf.org/rfc/rfc959.txt" rel="nofollow">RFC-959</a>, <a href="http://www.ietf.org/rfc/rfc2246.txt" rel="nofollow">RFC-2246</a>, <a href="http://www.ietf.org/rfc/rfc2228.txt" rel="nofollow">RFC-2228</a> </li><li><a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">Link</a> </li></ul><p><table><tbody><tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Command</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Implemented</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Milestone</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Description</strong> </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> <strong>Notes</strong> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> AUTH </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Authentication/Security Mechanism. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> CCC </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> NO </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Clear Command Channel. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PBSZ </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Protection Buffer Size. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Implemented as as a no-op as recommended. </td></tr> <tr><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> PROT </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> YES </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> --- </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Data Channel Protection Level. </td><td style="border: 1px solid rgb(170, 170, 170); padding: 5px;"> Support only "P" and "C" protection levels. </td></tr> </tbody></table></p>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+
+
+
+
+<script type="text/javascript" src="rfcs-compliance_files/dit_scripts.js"></script>
+
+
+
+ </body></html>
diff --git a/chromium/third_party/pyftpdlib/src/doc/roadmap.lnk.html b/chromium/third_party/pyftpdlib/src/doc/roadmap.lnk.html
new file mode 100644
index 00000000000..5a5fbf1afe5
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/roadmap.lnk.html
@@ -0,0 +1,5 @@
+<title>Redirecting to Roadmap...</title>
+<a href="http://code.google.com/p/pyftpdlib"><img id="logo" src="logo.png" /></a>
+
+<p>Redirecting to: <a href="http://code.google.com/p/pyftpdlib/wiki/Roadmap">http://code.google.com/p/pyftpdlib/wiki/Roadmap</a><br></p>
+<p>You'll be redirected in 3 seconds...</p>
diff --git a/chromium/third_party/pyftpdlib/src/doc/tutorial.html b/chromium/third_party/pyftpdlib/src/doc/tutorial.html
new file mode 100644
index 00000000000..c842da03e0c
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/doc/tutorial.html
@@ -0,0 +1,427 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style>
+.section_anchor {
+ font-size:0px;
+}
+</style>
+
+
+
+
+
+ <title>Tutorial</title>
+ </head><body>
+
+
+
+
+ <div id="wikicontent">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+
+ <td class="vt" id="wikimaincol" width="100%">
+
+ <div id="wikiheader" style="margin-bottom: 1em;">
+
+ &nbsp;
+ <span style="font-size: 120%; font-weight: bold;">Tutorial</span>
+ &nbsp;
+
+
+ <div style="font-style: italic; margin-top: 3px;">Tutorial containing API reference and example usages</div>
+
+ </div>
+ <h1><a name="Table_of_contents"></a>Table of contents<a href="#Table_of_contents" class="section_anchor">¶</a></h1><p></p><ul><li><a href="#Table_of_contents">Table of contents</a></li><li><a href="#1.0_-_Introduction">1.0 - Introduction</a></li><li><a href="#2.0_-_API_reference">2.0 - API reference</a></li><li><a href="#2.1_-_Contrib_package">2.1 - Contrib package</a></li><ul><li><a href="#2.2_-_pyftpdlib.contrib.handlers_module">2.2 - pyftpdlib.contrib.handlers module</a></li><li><a href="#2.3_-_pyftpdlib.contrib.authorizers_module">2.3 - pyftpdlib.contrib.authorizers module</a></li><li><a href="#2.4_-_pyftpdlib.contrib.filesystems_module">2.4 - pyftpdlib.contrib.filesystems module</a></li></ul><li><a href="#3.0_-_Customizing_your_FTP_server">3.0 - Customizing your FTP server</a></li><ul><li><a href="#3.1_-_Building_a_Base_FTP_server">3.1 - Building a Base FTP server</a></li><li><a href="#3.2_-_Logging_management">3.2 - Logging management</a></li><li><a href="#3.3_-_Storing_passwords_as_hash_digests">3.3 - Storing passwords as hash digests</a></li><li><a href="#3.4_-_Unix_FTP_Server">3.4 - Unix FTP Server</a></li><li><a href="#3.5_-_Windows_NT_FTP_Server">3.5 - Windows NT FTP Server</a></li><li><a href="#3.6_-_Throttle_bandwidth">3.6 - Throttle bandwidth</a></li><li><a href="#3.7_-_FTPS_%28FTP_over_TLS/SSL%29_server">3.7 - FTPS (FTP over TLS/SSL) server</a></li><li><a href="#3.8_-_Event_callbacks">3.8 - Event callbacks</a></li><li><a href="#3.9_-_Command_line_usage">3.9 - Command line usage</a></li></ul></ul> <p></p><h1><a name="1.0_-_Introduction"></a>1.0 - Introduction<a href="#1.0_-_Introduction" class="section_anchor">¶</a></h1><p>pyftpdlib implements the server side of the FTP protocol as defined in <a href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a>. pyftpdlib consist of a single file, <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/pyftpdlib/ftpserver.py" rel="nofollow">ftpserver.py</a>, which contains a hierarchy of classes, functions and variables which implement the backend functionality for the ftpd. <br> This document is intended to serve as a simple <a href="http://code.google.com/p/billiejoex/wiki/Tutorial#2.0_-_API_reference" rel="nofollow">API reference</a> of most important classes and functions. Also included is an introduction to <a href="http://code.google.com/p/billiejoex/wiki/Tutorial#3.0_-_Customizing_your_FTP_server" rel="nofollow">customization</a> through the use of some example scripts. Some of them are included in <a href="http://code.google.com/p/pyftpdlib/source/browse/#svn/trunk/demo" rel="nofollow">demo</a> directory of pyftpdlib source distribution. </p><p>If
+ you have written a customized configuration you think could be useful
+to the community feel free to share it by adding a comment at the end of
+ this document. </p><h1><a name="2.0_-_API_reference"></a>2.0 - API reference<a href="#2.0_-_API_reference" class="section_anchor">¶</a></h1><p><i>function</i> pyftpdlib.ftpserver.<strong>log</strong><font size="3"><strong><tt>(</tt></strong></font><i>msg</i><font size="3"><strong><tt>)</tt></strong></font> </p><blockquote>Log messages intended for the end user.
+</blockquote><hr><i>function</i> pyftpdlib.ftpserver.<strong>logline</strong><font size="3"><strong><tt>(</tt></strong></font><i>msg</i><font size="3"><strong><tt>)</tt></strong></font> <blockquote>Log commands and responses passing through the command channel.
+</blockquote><hr><i>function</i> pyftpdlib.ftpserver.<strong>logerror</strong><font size="3"><strong><tt>(</tt></strong></font><i>msg</i><font size="3"><strong><tt>)</tt></strong></font> <blockquote>Log traceback outputs occurring in case of errors.
+</blockquote><hr><i>class</i> pyftpdlib.ftpserver.<strong>AuthorizerError</strong><font size="3"><strong><tt>()</tt></strong></font> <blockquote>Base class for authorizers exceptions.
+</blockquote><hr><i>class</i> pyftpdlib.ftpserver.<strong>DummyAuthorizer</strong><font size="3"><strong><tt>()</tt></strong></font> <p></p><blockquote>Basic
+ "dummy" authorizer class, suitable for subclassing to create your own
+custom authorizers. An "authorizer" is a class handling authentications
+and permissions of the FTP server. It is used inside <tt>FTPHandler</tt>
+ class for verifying user's password, getting users home directory,
+checking user permissions when a filesystem read/write event occurs and
+changing user before accessing the filesystem. <tt>DummyAuthorizer</tt>
+is the base authorizer, providing a platform independent interface for
+managing "virtual" FTP users. Typically the first thing you have to do
+is create an instance of this class and start adding ftp users:
+</blockquote><pre class="prettyprint">&gt;&gt;&gt; from pyftpdlib import ftpserver
+&gt;&gt;&gt; authorizer = ftpserver.DummyAuthorizer()
+&gt;&gt;&gt; authorizer.add_user('user', 'password', '/home/user', perm='elradfmw')
+&gt;&gt;&gt; authorizer.add_anonymous('/home/nobody')</pre><ul><li><strong>add_user</strong><font size="3"><strong><tt>(</tt></strong></font><i>username</i>, <i>password</i>, <i>homedir</i><font size="3"><strong><tt>[</tt></strong></font>, <i>perm="elr"</i><font size="3"><strong><tt>[</tt></strong></font>, <i>msg_login="Login successful."</i><font size="3"><strong><tt>[</tt></strong></font>, <i>msg_quit="Goodbye."</i> <font size="3"><strong><tt>]]])</tt></strong></font> <br>Add a user to the virtual users table. <tt>AuthorizerError</tt> exceptions raised on error conditions such as insufficient permissions or duplicate usernames. Optional <tt>perm</tt>
+ argument is a set of letters referencing the user's permissions. Every
+ letter is used to indicate that the access rights the current FTP user
+has over the following specific actions are granted. The available
+permissions are the following listed below: </li></ul><blockquote>Read permissions:
+<ul><li><strong>"e"</strong> = change directory (CWD command) </li><li><strong>"l"</strong> = list files (LIST, NLST, STAT, MLSD, MLST commands) </li><li><strong>"r"</strong> = retrieve file from the server (RETR command) </li></ul>Write permissions
+<ul><li><strong>"a"</strong> = append data to an existing file (APPE command) </li><li><strong>"d"</strong> = delete file or directory (DELE, RMD commands) </li><li><strong>"f"</strong> = rename file or directory (RNFR, RNTO commands) </li><li><strong>"m"</strong> = create directory (MKD command) </li><li><strong>"w"</strong> = store a file to the server (STOR, STOU commands) </li></ul></blockquote><blockquote>Optional <tt>msg_login</tt> and <tt>msg_quit</tt> arguments can be specified to provide customized response strings when user log-in and quit. The <tt>perm</tt> argument of the <tt>add_user()</tt>
+ method refers to user's permissions. Every letter is used to indicate
+that the access rights the current FTP user has over the following
+specific actions are granted.
+</blockquote><ul><li><strong>add_anonymous</strong><font size="3"><strong><tt>(</tt></strong></font><i>homedir</i><font size="3"><strong><tt>[</tt></strong></font>, <tt>**</tt><i>kwargs</i><font size="3"><strong><tt>])</tt></strong></font><br>Add an anonymous user to the virtual users table. <tt>AuthorizerError</tt>
+ exception raised on error conditions such as insufficient permissions,
+missing home directory, or duplicate anonymous users. The keyword
+arguments in <tt>kwargs</tt> are the same expected by <tt>add_user()</tt> method: <tt>perm</tt>, <tt>msg_login</tt> and <tt>msg_quit</tt>. The optional <i>perm</i> keyword argument is a string defaulting to <tt>"elr"</tt> referencing "read-only" anonymous user's permission. Using a "write" value results in a <tt>RuntimeWarning</tt>. </li></ul><ul><li><strong>override_perm</strong><font size="3"><strong><tt>(</tt></strong></font><i>directory</i>, <i>perm</i><font size="3"><strong><tt>[</tt></strong></font>, <i>recursive=False</i><font size="3"><strong><tt>])</tt></strong></font><br>Override permissions for a given directory. <i><strong>New in version 0.5.0</strong></i> </li></ul><ul><li><strong>validate_authentication</strong><font size="3"><strong><tt>(</tt></strong></font><i>username</i>, <i>password</i><font size="3"><strong><tt>)</tt></strong></font><br>Return <tt>True</tt> if the supplied <tt>username</tt> and <tt>password</tt> match the stored credentials. </li></ul><ul><li><strong>impersonate_user</strong><font size="3"><strong><tt>(</tt></strong></font><i>username</i>, <i>password</i><font size="3"><strong><tt>)</tt></strong></font><br>Impersonate
+ another user (noop). It is always called before accessing the
+filesystem. By default it does nothing. The subclass overriding this
+method is expected to provide a mechanism to change the current user. </li></ul><ul><li><strong>terminate_impersonation</strong><font size="3"><strong><tt>(</tt></strong></font><i>username</i>, <i>password</i><font size="3"><strong><tt>)</tt></strong></font><br>Terminate
+ impersonation (noop). It is always called after having accessed the
+filesystem. By default it does nothing. The subclass overriding this
+method is expected to provide a mechanism to switch back to the original
+ user. </li></ul><ul><li><strong>remove_user</strong><font size="3"><strong><tt>(</tt></strong></font><i>username</i><font size="3"><strong><tt>)</tt></strong></font><br>Remove a user from the virtual user table. </li></ul><hr><p>class pyftpdlib.ftpserver.<strong>FTPHandler</strong><font size="3"><strong><tt>(</tt></strong></font><i>conn, server</i><font size="3"><strong><tt>)</tt></strong></font> </p><blockquote>This class implements the FTP server Protocol Interpreter (see <a href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a>),
+ handling commands received from the client on the control channel by
+calling the command's corresponding method (e.g. for received command
+"MKD pathname", <tt>ftp_MKD()</tt> method is called with <tt>pathname</tt> as the argument). All relevant session information are stored in instance variables. <tt>conn</tt> is the underlying socket object instance of the newly established connection, <tt>server</tt> is the <tt>FTPServer</tt> class instance. Basic usage simply requires creating an instance of <tt>FTPHandler</tt> class and specify which authorizer instance it will going to use:
+</blockquote><pre class="prettyprint">&gt;&gt;&gt; ftp_handler = ftpserver.FTPHandler
+&gt;&gt;&gt; ftp_handler.authorizer = authorizer</pre><blockquote>All
+relevant session information is stored in class attributes reproduced
+below and can be modified before instantiating this class:
+</blockquote><ul><li><strong>timeout</strong><br>The timeout which is
+the maximum time a remote client may spend between FTP commands. If the
+timeout triggers, the remote client will be kicked off (defaults to <tt>300</tt> seconds). <i><strong>New in version 5.0</strong></i> </li></ul><ul><li><strong>banner</strong><br>String sent when client connects (default <tt>"pyftpdlib %s ready." %__ver__</tt>). </li></ul><ul><li><strong>max_login_attempts</strong><br>Maximum number of wrong authentications before disconnecting (default <tt>3</tt>). </li></ul><ul><li><strong>permit_foreign_addresses</strong><br>Whether enable <a href="http://www.proftpd.org/docs/howto/FXP.html" rel="nofollow">FXP</a> feature (default <tt>False</tt>). </li></ul><ul><li><strong>permit_privileged_ports</strong><br>Set to <tt>True</tt> if you want to permit active connections (PORT) over privileged ports (not recommended, default <tt>False</tt>). </li></ul><ul><li><strong>masquerade_address</strong><br>The
+ "masqueraded" IP address to provide along PASV reply when pyftpdlib is
+running behind a NAT or other types of gateways. When configured
+pyftpdlib will hide its local address and instead use the public address
+ of your NAT (default <tt>None</tt>). </li></ul><ul><li><strong>masquerade_address_map</strong><br>In
+ case the server has multiple IP addresses which are all behind a NAT
+router, you may wish to specify individual masquerade_addresses for each
+ of them. The map expects a dictionary containing private IP addresses
+as keys, and their corresponding public (masquerade) addresses as values
+ (defaults to <tt>{}</tt>). <i><strong>New in version 0.6.0</strong></i> </li></ul><ul><li><strong>passive_ports</strong><br>What ports ftpd will use for its passive data transfers. Value expected is a list of integers (e.g. <tt>range(60000, 65535)</tt>). When configured pyftpdlib will no longer use kernel-assigned random ports (default <tt>None</tt>). </li></ul><ul><li><strong>use_gmt_times</strong><br>When True causes the server to report all ls and MDTM times in GMT and not local time (default <tt>True</tt>). <i><strong>New in version 0.6.0</strong></i> </li></ul><ul><li><strong>tcp_no_delay</strong><br>Controls
+ the use of the TCP_NODELAY socket option which disables the Nagle
+algorithm resulting in significantly better performances (default <tt>True</tt> on all platforms where it is supported). <i><strong>New in version 0.6.0</strong></i> </li></ul><blockquote><br>Follows a list of callback methods that can be overridden in a subclass. For blocking operations read the FAQ on how to <a href="http://code.google.com/p/pyftpdlib/wiki/FAQ#How_can_I_run_long-running_tasks_without_blocking_the_server?" rel="nofollow">run time consuming tasks</a>
+</blockquote><ul><li><strong>on_login</strong><font size="3"><strong><tt>(</tt></strong></font><i>username</i><font size="3"><strong><tt>)</tt></strong></font><br>Called on user login. <i><strong>New in version 0.6.0</strong></i> </li></ul><ul><li><strong>on_file_sent</strong><font size="3"><strong><tt>(</tt></strong></font><i>file</i><font size="3"><strong><tt>)</tt></strong></font><br>Called every time a file has been successfully sent. <i><strong>New in version 0.5.1</strong></i> </li></ul><ul><li><strong>on_file_received</strong><font size="3"><strong><tt>(</tt></strong></font><i>file</i><font size="3"><strong><tt>)</tt></strong></font><br>Called every time a file has been successfully received. <i><strong>New in version 0.5.1</strong></i> </li></ul><ul><li><strong>on_incomplete_file_sent</strong><font size="3"><strong><tt>(</tt></strong></font><i>file</i><font size="3"><strong><tt>)</tt></strong></font><br>Called every time a file has not been entirely sent (e.g. transfer aborted via ABOR or client disconnected abruptly). <tt>file</tt> is the absolute name of that file. <i><strong>New in version 0.6.0</strong></i> </li></ul><ul><li><strong>on_incomplete_file_received</strong><font size="3"><strong><tt>(</tt></strong></font><i>file</i><font size="3"><strong><tt>)</tt></strong></font><br>Called every time a file has not been entirely received (e.g. transfer aborted via ABOR or client disconnected abruptly). <tt>file</tt> is the absolute name of that file. <i><strong>New in version 0.6.0</strong></i> </li></ul><hr><p><i>class</i> pyftpdlib.ftpserver.<strong>DTPHandler</strong><font size="3"><strong><tt>(</tt></strong></font><i>sock_obj, cmd_channel</i><font size="3"><strong><tt>)</tt></strong></font> </p><blockquote>This class handles the server-data-transfer-process (server-DTP, see <a href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a>) managing all transfer operations regarding the data channel. <tt>sock_obj</tt> is the underlying socket object instance of the newly established connection, <tt>cmd_channel</tt> is the <tt>FTPHandler</tt> class instance.
+</blockquote><ul><li><strong>timeout</strong><br>The timeout which
+roughly is the maximum time we permit data transfers to stall for with
+no progress. If the timeout triggers, the remote client will be kicked
+off. <i><strong>New in version 5.0</strong></i> </li></ul><ul><li><strong>ac_in_buffer_size</strong><br> </li><li><strong>ac_out_buffer_size</strong><br>The buffer sizes to use when receiving and sending data (both defaulting to <tt>65536</tt>
+ bytes). For LANs you may want this to be fairly large. Depending on
+available memory and number of connected clients setting them to a lower
+ value can result in better performances. </li></ul><hr><p><i>class</i> pyftpdlib.ftpserver.<strong>ThrottledDTPHandler</strong><font size="3"><strong><tt>(</tt></strong></font><i>sock_obj, cmd_channel</i><font size="3"><strong><tt>)</tt></strong></font> </p><blockquote>A <tt>DTPHandler</tt>
+ subclass which wraps sending and receiving in a data counter and
+temporarily "sleeps" the channel so that you burst to no more than x
+Kb/sec average. Use it instead of <tt>DTPHandler</tt> to set transfer rates limits for both downloads and/or uploads (see the <a href="http://code.google.com/p/pyftpdlib/source/browse/trunk/demo/throttled_ftpd.py" rel="nofollow">demo script</a> showing the example usage). <strong><i>New in version 0.5.2</i></strong>
+</blockquote><ul><li><strong>read_limit</strong><br>The maximum number of bytes to read (receive) in one second (defaults to 0 == no limit) </li></ul><ul><li><strong>write_limit</strong><br>The maximum number of bytes to write (send) in one second (defaults to 0 == no limit). </li></ul><hr><p><i>class</i> pyftpdlib.ftpserver.<strong>FTPServer</strong><font size="3"><strong><tt>(</tt></strong></font><i>address, handler</i><font size="3"><strong><tt>)</tt></strong></font> </p><blockquote>This class is an <tt>asyncore.dispatcher</tt> subclass. It creates a FTP socket listening on <tt>address</tt> (a tuple containing the ip:port pair), dispatching the requests to a "handler" (typically <tt>FTPHandler</tt> class object). It is typically used for starting asyncore polling loop:
+</blockquote><pre class="prettyprint">&gt;&gt;&gt; address = ('127.0.0.1', 21)
+&gt;&gt;&gt; ftpd = ftpserver.FTPServer(address, ftp_handler)
+&gt;&gt;&gt; ftpd.serve_forever()</pre><ul><li><strong>max_cons</strong><br>Number of maximum simultaneous connections accepted (default <tt>512</tt>). </li></ul><ul><li><strong>max_cons_per_ip</strong><br>Number of maximum connections accepted for the same IP address (default <tt>0</tt> == <i>no limit</i>). </li></ul><ul><li><strong>serve_forever(</strong><font size="3"><strong><tt>[</tt></strong></font><i>timeout=1</i><font size="3"><strong><tt>[</tt></strong></font>, <i>use_poll=False</i><font size="3"><strong><tt>[</tt></strong></font>, <i>map=None</i><font size="3"><strong><tt>[</tt></strong></font>, <i>count=None</i><font size="3"><strong><tt>]]])</tt></strong></font><br>A
+ wrap around asyncore.loop(); starts the asyncore polling loop including
+ running the scheduler. The arguments are the same expected by original <a href="http://docs.python.org/library/asyncore.html#asyncore.loop" rel="nofollow">asyncore.loop()</a> function. </li></ul><ul><li><strong>close</strong><font size="3"><strong><tt>()</tt></strong></font><br>Stop serving without disconnecting currently connected clients. </li></ul><ul><li><strong>close_all(</strong><font size="3"><strong><tt>[</tt></strong></font><i>map=None</i><font size="3"><strong><tt>[</tt></strong></font>, <i>ignore_all=False</i><font size="3"><strong><tt>]])</tt></strong></font><br>Stop serving disconnecting also the currently connected clients. The <tt>map</tt> parameter is a dictionary whose items are the channels to close. If <tt>map</tt> is omitted, the default <tt>asyncore.socket_map</tt> is used. Having <tt>ignore_all</tt> parameter set to <tt>False</tt> results in raising exception in case of unexpected errors. </li></ul><hr><p><i>class</i> pyftpdlib.ftpserver.<strong>AbstractedFS(</strong><i>root</i>, <i>cmd_channel</i><strong>)</strong> </p><blockquote>A
+ class used to interact with the file system, providing a cross-platform
+ interface compatible with both Windows and UNIX style filesystems where
+ all paths use "/" separator. <br><tt>AbstractedFS</tt> distinguishes
+between "real" filesystem paths and "virtual" ftp paths emulating a UNIX
+ chroot jail where the user can not escape its home directory (example:
+real "/home/user" path will be seen as "/" by the client). <br>It also provides some utility methods and wraps around all <tt>os.*</tt>
+ calls involving operations against the filesystem like creating files
+or removing directories. The contructor accepts two arguments: <tt>root</tt> which is the user "real" home directory (e.g. '/home/user') and <tt>cmd_channel</tt> which is the <tt>FTPHandler</tt> class instance.
+</blockquote><blockquote><i><strong>Changed in version 0.6.0</strong>: <tt>root</tt> and <tt>cmd_channel</tt> arguments were added.</i>
+</blockquote><ul><li><strong>root</strong><br>User's home directory ("real"). </li></ul><ul><li><strong>cwd</strong><br>User's current working directory ("virtual"). </li></ul><ul><li><strong>ftpnorm</strong><font size="3"><strong><tt>(</tt></strong></font><i>ftppath</i><font size="3"><strong><tt>)</tt></strong></font><br>Normalize
+ a "virtual" ftp pathname depending on the current working directory
+(e.g. having "/foo" as current working directory "x" becomes "/foo/x"). </li></ul><ul><li><strong>ftp2fs</strong><font size="3"><strong><tt>(</tt></strong></font><i>ftppath</i><font size="3"><strong><tt>)</tt></strong></font><br>Translate
+ a "virtual" ftp pathname into equivalent absolute "real" filesystem
+pathname (e.g. having "/home/user" as root directory "x" becomes
+"/home/user/x"). </li></ul><ul><li><strong>fs2ftp</strong><font size="3"><strong><tt>(</tt></strong></font><i>fspath</i><font size="3"><strong><tt>)</tt></strong></font><br>Translate
+ a "real" filesystem pathname into equivalent absolute "virtual" ftp
+pathname depending on the user's root directory (e.g. having
+"/home/user" as root directory "/home/user/x" becomes "/x". </li></ul><ul><li><strong>validpath</strong><font size="3"><strong><tt>(</tt></strong></font><i>path</i><font size="3"><strong><tt>)</tt></strong></font><br>Check
+ whether the path belongs to user's home directory. Expected argument is
+ a "real" filesystem path. If path is a symbolic link it is resolved to
+check its real destination. Pathnames escaping from user's root
+directory are considered not valid (return <tt>False</tt>). </li></ul><p> </p><hr><p></p><h1><a name="2.1_-_Contrib_package"></a>2.1 - Contrib package<a href="#2.1_-_Contrib_package" class="section_anchor">¶</a></h1><p>Starting from version 0.6.0 a new <tt>contrib</tt> package has been added to <tt>pyftpdlib</tt> namespace which extends base <tt>ftpserver.py</tt>
+ module. Modules contained in here usually requires third-party modules
+to be installed separately or are specific for a given Python version or
+ operating system. </p><hr><h2><a name="2.2_-_pyftpdlib.contrib.handlers_module"></a>2.2 - pyftpdlib.contrib.handlers module<a href="#2.2_-_pyftpdlib.contrib.handlers_module" class="section_anchor">¶</a></h2><p>This module provides basic support for FTPS (FTP over SSL/TLS) as described in <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a> implementing AUTH, PBSZ and PROT commands. In order to make it work <a href="http://pypi.python.org/pypi/pyOpenSSL" rel="nofollow">PyOpenSSL module</a> is required to be installed. <a href="http://code.google.com/p/billiejoex/wiki/Tutorial?ts=1284112249&amp;updated=Tutorial#3.7_-_FTPS_%28FTP_over_TLS/SSL%29_server" rel="nofollow">Example below</a> shows how to setup an FTPS server. </p><p><i>class</i> pyftpdlib.contrib.handlers.<strong>TLS_FTPHandler(</strong><i>conn</i>, <i>server</i><strong>)</strong> </p><blockquote>A <tt>ftpserver.FTPHandler</tt> subclass supporting TLS/SSL. Configurable attributes:
+</blockquote><p></p><ul><li><strong>certfile</strong><br>The path to a
+file which contains a certificate to be used to identify the local side
+of the connection. This must always be specified, unless context is
+provided instead. </li></ul><ul><li><strong>keyfile</strong><br>The path of the file containing the private RSA key; can be omittetted if certfile already contains the private key (defaults: <tt>None</tt>). </li></ul><ul><li><strong>tls_control_required</strong><br>When <tt>True</tt>
+ requires SSL/TLS to be established on the control channel, before
+logging in. This means the user will have to issue AUTH before USER/PASS
+ (default <tt>False</tt>). </li></ul><ul><li><strong>tls_data_required</strong><br>When <tt>True</tt>
+ requires SSL/TLS to be established on the data channel. This means the
+user will have to issue PROT before PASV or PORT (default <tt>False</tt>). </li></ul><p> </p><hr><p></p><h2><a name="2.3_-_pyftpdlib.contrib.authorizers_module"></a>2.3 - pyftpdlib.contrib.authorizers module<a href="#2.3_-_pyftpdlib.contrib.authorizers_module" class="section_anchor">¶</a></h2><p>This
+ module contains two classes for handling users on Unix and Windows
+systems. Users are no longer supposed to be explicitly added as when
+using <tt>DummyAuthorizer</tt>.<br>All FTP users are the same defined on
+ the UNIX or Windows system so if you access on your system by using
+"john" as username and "12345" as password those same credentials can be
+ used for accessing the FTP server as well.<br>The user home directories
+ will be automatically determined when user logins (e.g. /home/user on
+Unix, C:\Documents and settings\user on Windows).<br>Every time a
+filesystem operation occurs (e.g. a file is created or deleted) the id
+of the process is temporarily changed to the effective user id and
+whether the operation will succeed depends on user and file permissions.
+ This is why full read and write permissions are granted by default in
+the class constructors. </p><p><i>class</i> pyftpdlib.contrib.authorizers.<strong>UnixAuthorizer(</strong><i>global_perm="elradfmw"</i>, <i>allowed_users=<tt>[]</tt></i>, <i>rejected_users=<tt>[]</tt></i>, <i>require_valid_shell=True</i>, <i>anonymous_user=None</i>, ,<i>msg_login="Login successful."</i>, <i>msg_quit="Goodbye."</i><strong>)</strong> </p><blockquote><tt>global_perm</tt> is a series of letters referencing the users permissions; defaults to <tt>"elradfmw"</tt> which means full read and write access for everybody (except anonymous). <tt>allowed_users</tt> and <tt>rejected_users</tt> options expect a list of users which are accepted or rejected for authenticating against the FTP server; defaults both to <tt>[]</tt> (no restrictions). <tt>require_valid_shell</tt>
+ deny access for those users which do not have a valid shell binary
+listed in /etc/shells. If /etc/shells cannot be found this is a no-op.
+Anonymous user is not subject to this option, and is free to not have a
+valid shell defined. Defaults to <tt>True</tt> (a valid shell is required for login). <tt>anonymous_user</tt>
+ can be specified if you intend to provide anonymous access. The value
+expected is a string representing the system user to use for managing
+anonymous sessions; defaults to <tt>None</tt> (anonymous access disabled).<br> Note that in order to use this class super user privileges are required.
+</blockquote><p> </p><blockquote><i><strong>New in version 0.6.0</strong></i>
+</blockquote><p></p><ul><li><strong>override_user(</strong><i>username=None</i>, <i>password=None</i>, <i>homedir=None</i>, <i>perm=None</i>, <i>anonymous_user=None</i>, <i>msg_login=None</i>, <i>msg_quit=None</i><strong>)</strong><br>Overrides one or more options specified in the class constructor for a specific user. </li></ul><blockquote>Examples:
+</blockquote><pre class="prettyprint">&gt;&gt;&gt; from pyftpdlib.contrib.authorizers import UnixAuthorizer
+&gt;&gt;&gt; # accept all except root
+&gt;&gt;&gt; auth = UnixAuthorizer(rejected_users=["root"])
+&gt;&gt;&gt; # accept some users only
+&gt;&gt;&gt; auth = UnixAuthorizer(allowed_users=["matt", "jay"])
+&gt;&gt;&gt; # accept everybody and don't care if they have not a valid shell
+&gt;&gt;&gt; auth = UnixAuthorizer(require_valid_shell=False)
+&gt;&gt;&gt; # set specific options for a user
+&gt;&gt;&gt; auth.override_user("matt", password="foo", perm="elr")</pre><hr><p><i>class</i> pyftpdlib.contrib.authorizers.<strong>WindowsAuthorizer(</strong><i>global_perm="elradfmw"</i>, <i>allowed_users=<tt>[]</tt></i>, <i>rejected_users=<tt>[]</tt></i>, <i>anonymous_user=None</i>, <i>anonymous_password=""</i>, <i>msg_login="Login successful."</i>, <i>msg_quit="Goodbye."</i><strong>)</strong>: </p><blockquote>Same as <tt>UnixAuthorizer</tt> except for <i>anonymous_password</i> argument which must be specified when defining the <i>anonymous_user</i>.<br>Also <i>requires_valid_shell</i> option is not available. In order to use this class <a href="http://sourceforge.net/projects/pywin32/" rel="nofollow">pywin32 extension</a> must be installed.
+</blockquote><blockquote><i><strong>New in version 0.6.0</strong></i>
+</blockquote><p> </p><hr><p></p><h2><a name="2.4_-_pyftpdlib.contrib.filesystems_module"></a>2.4 - pyftpdlib.contrib.filesystems module<a href="#2.4_-_pyftpdlib.contrib.filesystems_module" class="section_anchor">¶</a></h2><p>class pyftpdlib.contrib.filesystems.<strong>UnixFilesystem(</strong><i>root</i>, <i>cmd_channel</i><strong>)</strong> </p><blockquote>Represents the real UNIX filesystem. Differently from AbstractedFS the client will login into <tt>/home/&lt;username&gt;</tt> and will be able to escape its home directory and navigate the real filesystem. Use it in conjuction with <tt>UnixAuthorizer</tt> to implement a "real" UNIX FTP server (see <a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/unix_ftpd.py" rel="nofollow">demo/unix_ftpd.py</a>).
+</blockquote><p></p><blockquote><i><strong>New in version 0.6.0</strong></i>
+</blockquote><hr><h1><a name="3.0_-_Customizing_your_FTP_server"></a>3.0 - Customizing your FTP server<a href="#3.0_-_Customizing_your_FTP_server" class="section_anchor">¶</a></h1><p>Below
+ is a set of example scripts showing some of the possible customizations
+ that can be done with pyftpdlib. Some of them are included in <a href="http://code.google.com/p/pyftpdlib/source/browse/#svn/trunk/demo" rel="nofollow">demo</a> directory of pyftpdlib source distribution. </p><h2><a name="3.1_-_Building_a_Base_FTP_server"></a>3.1 - Building a Base FTP server<a href="#3.1_-_Building_a_Base_FTP_server" class="section_anchor">¶</a></h2><p>The
+ script below is a basic configuration, and it's probably the best
+starting point for understanding how things work. It uses the base <tt>DummyAuthorizer</tt> for adding a bunch of "virtual" users. </p><p>It also sets a limit for connections by overriding <tt>FTPServer.max_cons</tt> and <tt>FTPServer.max_cons_per_ip</tt>
+ attributes which are intended to set limits for maximum connections to
+handle simultaneously and maximum connections from the same IP address.
+Overriding these variables is always a good idea (they default to <tt>0</tt>, or "no limit") since they are a good workaround for avoiding DoS attacks. </p><p><a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/basic_ftpd.py" rel="nofollow">download script</a> </p><pre class="prettyprint">#!/usr/bin/env python
+
+"""A basic FTP server which uses a DummyAuthorizer for managing 'virtual
+users', setting a limit for incoming connections.
+"""
+
+from pyftpdlib import ftpserver
+
+
+def main():
+ # Instantiate a dummy authorizer for managing 'virtual' users
+ authorizer = ftpserver.DummyAuthorizer()
+
+ # Define a new user having full r/w permissions and a read-only
+ # anonymous user
+ authorizer.add_user('user', password="12345", homedir='.', perm='elradfmw')
+ authorizer.add_anonymous(homedir='.')
+
+ # Instantiate FTP handler class
+ handler = ftpserver.FTPHandler
+ handler.authorizer = authorizer
+
+ # Define a customized banner (string returned when client connects)
+ handler.banner = "pyftpdlib %s based ftpd ready." %ftpserver.__ver__
+
+ # Specify a masquerade address and the range of ports to use for
+ # passive connections. Decomment in case you're behind a NAT.
+ #ftp_handler.masquerade_address = '151.25.42.11'
+ #ftp_handler.passive_ports = range(60000, 65535)
+
+ # Instantiate FTP server class and listen to 0.0.0.0:21
+ address = ('', 21)
+ server = ftpserver.FTPServer(address, handler)
+
+ # set a limit for connections
+ server.max_cons = 256
+ server.max_cons_per_ip = 5
+
+ # start ftp server
+ server.serve_forever()
+
+if __name__ == '__main__':
+ main()</pre><h2><a name="3.2_-_Logging_management"></a>3.2 - Logging management<a href="#3.2_-_Logging_management" class="section_anchor">¶</a></h2><p>As mentioned, ftpserver.py comes with 3 different functions intended for a separate logging system: <tt>log()</tt>, <tt>logline()</tt> and <tt>logerror()</tt>. Let's suppose you don't want to print FTPd messages on screen but you want to write them into different files: <i>"/var/log/ftpd.log"</i> will be main log file, <i>"/var/log/ftpd.lines.log"</i> the one where you'll want to store commands and responses passing through the control connection. </p><p>Here's one method this could be implemented: </p><pre class="prettyprint">#!/usr/bin/env python
+
+import os
+import time
+
+from pyftpdlib import ftpserver
+
+now = lambda: time.strftime("[%Y-%b-%d %H:%M:%S]")
+
+def standard_logger(msg):
+ f1.write("%s %s\n" %(now(), msg))
+
+def line_logger(msg):
+ f2.write("%s %s\n" %(now(), msg))
+
+if __name__ == "__main__":
+ f1 = open('ftpd.log', 'a')
+ f2 = open('ftpd.lines.log', 'a')
+ ftpserver.log = standard_logger
+ ftpserver.logline = line_logger
+
+ authorizer = ftpserver.DummyAuthorizer()
+ authorizer.add_anonymous(os.getcwd())
+ handler = ftpserver.FTPHandler
+ handler.authorizer = authorizer
+ address = ('', 21)
+ server = ftpserver.FTPServer(address, handler)
+ server.serve_forever()</pre><h2><a name="3.3_-_Storing_passwords_as_hash_digests"></a>3.3 - Storing passwords as hash digests<a href="#3.3_-_Storing_passwords_as_hash_digests" class="section_anchor">¶</a></h2><p>Using FTP server library with the default <tt>DummyAuthorizer</tt>
+ means that password will be stored in clear-text. An end-user ftpd
+using the default dummy authorizer would typically require a
+configuration file for authenticating users and their passwords but
+storing clear-text passwords is of course undesirable. </p><p>The most
+common way to do things in such case would be first creating new users
+and then storing their usernames + passwords as hash digests into a file
+ or wherever you find it convenient. </p><p>The example below shows how
+to easily create an encrypted account storage system by storing
+passwords as one-way hashes by using md5 algorithm. This could be easily
+ done by using the <strong>hashlib</strong> module included with Python stdlib and by sub-classing the original <tt>DummyAuthorizer</tt> class overriding its <tt>validate_authentication()</tt> method. </p><p><a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/md5_ftpd.py" rel="nofollow">download script</a> </p><pre class="prettyprint">#!/usr/bin/env python
+
+"""A basic ftpd storing passwords as hash digests."""
+
+import os
+try:
+ from hashlib import md5
+except ImportError:
+ # backward compatibility with Python &lt; 2.5
+ from md5 import new as md5
+
+from pyftpdlib import ftpserver
+
+
+class DummyMD5Authorizer(ftpserver.DummyAuthorizer):
+
+ def validate_authentication(self, username, password):
+ hash = md5(password).hexdigest()
+ return self.user_table[username]['pwd'] == hash
+
+if __name__ == "__main__":
+ # get a hash digest from a clear-text password
+ hash = md5('12345').hexdigest()
+ authorizer = DummyMD5Authorizer()
+ authorizer.add_user('user', hash, os.getcwd(), perm='elradfmw')
+ authorizer.add_anonymous(os.getcwd())
+ handler = ftpserver.FTPHandler
+ handler.authorizer = authorizer
+ address = ('', 21)
+ server = ftpserver.FTPServer(address, handler)
+ server.serve_forever()</pre><h2><a name="3.4_-_Unix_FTP_Server"></a>3.4 - Unix FTP Server<a href="#3.4_-_Unix_FTP_Server" class="section_anchor">¶</a></h2><p>If
+ you're running a Unix system you may want to configure your ftpd to
+include support for "real" users existing on the system and navigate the
+ real filesystem. </p><p>The example below uses <tt>UnixAuthorizer</tt> and <tt>UnixFilesystem</tt> classes to do so. </p><pre class="prettyprint">#!/usr/bin/env python
+
+"""A FTPd using local UNIX account database to authenticate users.
+
+It temporarily impersonate the system users every time they are going
+to perform a filesystem operations.
+"""
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.authorizers import UnixAuthorizer
+from pyftpdlib.contrib.filesystems import UnixFilesystem
+
+
+def main():
+ authorizer = UnixAuthorizer(rejected_users=["root"], require_valid_shell=True)
+ handler = ftpserver.FTPHandler
+ handler.authorizer = authorizer
+ handler.abstracted_fs = UnixFilesystem
+ address = ('', 21)
+ server = ftpserver.FTPServer(address, handler)
+ server.serve_forever()
+
+if __name__ == "__main__":
+ main()</pre><h2><a name="3.5_-_Windows_NT_FTP_Server"></a>3.5 - Windows NT FTP Server<a href="#3.5_-_Windows_NT_FTP_Server" class="section_anchor">¶</a></h2><p>The
+ following code shows how to implement a basic authorizer for a Windows
+NT workstation to authenticate against existing Windows user accounts.
+This code requires Mark Hammond's <a href="http://starship.python.net/crew/mhammond/win32/" rel="nofollow">pywin32</a> extension to be installed. </p><p><a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/winnt_ftpd.py" rel="nofollow">download script</a> </p><pre class="prettyprint">#!/usr/bin/env python
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.authorizers import WindowsAuthorizer
+
+if __name__ == "__main__":
+ authorizer = WindowsAuthorizer()
+ # Use Guest user with empty password to handle anonymous sessions.
+ # Guest user must be enabled first, empty password set and profile
+ # directory specified.
+ #authorizer = WindowsAuthorizer(anonymous_user="Guest", anonymous_password="")
+ ftp_handler = ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+ address = ('', 21)
+ ftpd = ftpserver.FTPServer(address, ftp_handler)
+ ftpd.serve_forever()</pre><h2><a name="3.6_-_Throttle_bandwidth"></a>3.6 - Throttle bandwidth<a href="#3.6_-_Throttle_bandwidth" class="section_anchor">¶</a></h2><p>An
+ important feature for an ftpd is limiting the speed for downloads and
+uploads affecting the data channel. Starting from version 0.5.2 it is
+possible to use the new <tt>ThrottledDTPHandler</tt> class to set such limits. The basic idea behind <tt>ThrottledDTPHandler</tt>
+ is to wrap sending and receiving in a data counter and temporary
+"sleep" the data channel so that you burst to no more than x Kb/sec
+average. When it realizes that more than x Kb in a second are being
+transmitted it temporary blocks the transfer for a certain number of
+seconds. </p><p>See <a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/throttled_ftpd.py" rel="nofollow">throttled_ftpd.py</a> demo script providing an example on how to use it. </p><h2><a name="3.7_-_FTPS_(FTP_over_TLS/SSL)_server"></a>3.7 - FTPS (FTP over TLS/SSL) server<a href="#3.7_-_FTPS_%28FTP_over_TLS/SSL%29_server" class="section_anchor">¶</a></h2><p>Starting from version 0.6.0 pyftpdlib finally includes full FTPS support implementing both TLS and SSL protocols and <strong>AUTH</strong>, <strong>PBSZ</strong> and <strong>PROT</strong> commands as defined in <a href="http://www.ietf.org/rfc/rfc4217.txt" rel="nofollow">RFC-4217</a>. This has been implemented by using <a href="http://pypi.python.org/pypi/pyOpenSSL" rel="nofollow">PyOpenSSL</a> module, which is required in order to run the code below. <tt>TLS_FTPHandlerFactory</tt> class requires a <tt>certfile</tt> and a <tt>keyfile</tt> to be specified. <a href="http://www.modssl.org/docs/2.7/ssl_faq.html#ToC24" rel="nofollow">Apache FAQs</a>
+ provide instructions on how to generate them. If you don't care about
+having your personal self-signed certificates you can use the one in the
+ demo directory which include both and is available <a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/keycert.pem" rel="nofollow">here</a>. </p><p><a href="http://pyftpdlib.googlecode.com/svn/trunk/demo/tls_ftpd.py" rel="nofollow">download script</a> </p><pre class="prettyprint">#!/usr/bin/env python
+
+"""An RFC-4217 asynchronous FTPS server supporting both SSL and TLS.
+
+Requires PyOpenSSL module (http://pypi.python.org/pypi/pyOpenSSL).
+"""
+
+from pyftpdlib import ftpserver
+from pyftpdlib.contrib.handlers import TLS_FTPHandlerFactory
+
+
+if __name__ == '__main__':
+ authorizer = ftpserver.DummyAuthorizer()
+ authorizer.add_user('user', '12345', '.', perm='elradfmw')
+ authorizer.add_anonymous('.')
+ handler = TLS_FTPHandlerFactory('demo/keycert.pem')
+ handler.authorizer = authorizer
+ # requires SSL for both control and data channel
+ #ftp_handler.tls_control_required = True
+ #ftp_handler.tls_data_required = True
+ server = ftpserver.FTPServer(('', 8021), handler)
+ server.serve_forever()</pre><h2><a name="3.8_-_Event_callbacks"></a>3.8 - Event callbacks<a href="#3.8_-_Event_callbacks" class="section_anchor">¶</a></h2><p>A small example which shows how to use callback methods via <tt>FTPHandler</tt> subclassing: </p><pre class="prettyprint">from pyftpdlib import ftpserver
+
+class MyHandler(ftpserver.FTPHandler):
+
+ def on_login(self, username):
+ # do something when user login
+ pass
+
+ def on_file_sent(self, file):
+ self.log("user %s just finished downloading file %s" % (self.username, file))
+
+ def on_file_received(self, file):
+ import shutil
+ if file.endswith('.avi'):
+ shutil.move(file, "my-videos")
+
+ def on_incomplete_file_sent(self, file):
+ # do something when a file is partially sent
+ pass
+
+ def on_incomplete_file_received(self, file):
+ # remove partially uploaded files
+ os.remove(file)
+
+
+def run():
+ authorizer = ftpserver.DummyAuthorizer()
+ authorizer.add_user('user', '12345', homedir='.', perm='elradfmw')
+ authorizer.add_anonymous(homedir='.')
+
+ handler = MyHandler
+ handler.authorizer = authorizer
+ server = ftpserver.FTPServer(('', 21), handler)
+ server.serve_forever()
+
+if __name__ == "__main__":
+ run()
+</pre><h2><a name="3.9_-_Command_line_usage"></a>3.9 - Command line usage<a href="#3.9_-_Command_line_usage" class="section_anchor">¶</a></h2><p>Starting
+ from version 0.6.0 pyftpdlib can be run as a simple stand-alone server
+via Python's -m option, which is particularly useful when you want to
+quickly share a directory. Some examples. </p><p>Anonymous FTPd sharing current directory: </p><p><tt>python -m pyftpdlib.ftpserver</tt> </p><p>Anonymous FTPd with write permission: </p><p><tt>python -m pyftpdlib.ftpserver -w</tt> </p><p>Set a different address/port and home directory: </p><p><tt>python -m pyftpdlib.ftpserver -i localhost -p 8021 -d /home/someone</tt> </p><p>See <tt>python -m pyftpdlib.ftpserver -h</tt> for a complete list of options. </p>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+
+
+
+
+<script type="text/javascript" src="tutorial_files/dit_scripts.js"></script>
+
+
+
+ </body></html>
diff --git a/chromium/third_party/pyftpdlib/src/pyftpdlib/__init__.py b/chromium/third_party/pyftpdlib/src/pyftpdlib/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/pyftpdlib/__init__.py
diff --git a/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/__init__.py b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/__init__.py
diff --git a/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/authorizers.py b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/authorizers.py
new file mode 100644
index 00000000000..8ad2aa91340
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/authorizers.py
@@ -0,0 +1,620 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""An "authorizer" is a class handling authentications and permissions
+of the FTP server. It is used by pyftpdlib.ftpserver.FTPHandler
+class for:
+
+- verifying user password
+- getting user home directory
+- checking user permissions when a filesystem read/write event occurs
+- changing user when accessing the filesystem
+
+This module contains two classes which implements such functionalities
+in a system-specific way for both Unix and Windows.
+"""
+
+__all__ = []
+
+
+import os
+import errno
+
+from pyftpdlib.ftpserver import DummyAuthorizer, AuthorizerError
+
+
+def replace_anonymous(callable):
+ """A decorator to replace anonymous user string passed to authorizer
+ methods as first arugument with the actual user used to handle
+ anonymous sessions.
+ """
+ def wrapper(self, username, *args, **kwargs):
+ if username == 'anonymous':
+ username = self.anonymous_user or username
+ return callable(self, username, *args, **kwargs)
+ return wrapper
+
+
+class _Base(object):
+ """Methods common to both Unix and Windows authorizers.
+ Not supposed to be used directly.
+ """
+
+ def __init__(self):
+ """Check for errors in the constructor."""
+ if self.rejected_users and self.allowed_users:
+ raise ValueError("rejected_users and allowed_users options are "
+ "mutually exclusive")
+
+ users = self._get_system_users()
+ for user in (self.allowed_users or self.rejected_users):
+ if user == 'anonymous':
+ raise ValueError('invalid username "anonymous"')
+ if user not in users:
+ raise ValueError('unknown user %s' % user)
+
+ if self.anonymous_user is not None:
+ if not self.has_user(self.anonymous_user):
+ raise ValueError('no such user %s' % self.anonymous_user)
+ home = self.get_home_dir(self.anonymous_user)
+ if not os.path.isdir(home):
+ raise ValueError('no valid home set for user %s'
+ % self.anonymous_user)
+
+ def override_user(self, username, password=None, homedir=None, perm=None,
+ msg_login=None, msg_quit=None):
+ """Overrides the options specified in the class constructor
+ for a specific user.
+ """
+ if not password and not homedir and not perm and not msg_login \
+ and not msg_quit:
+ raise ValueError("at least one keyword argument must be specified")
+ if self.allowed_users and username not in self.allowed_users:
+ raise ValueError('%s is not an allowed user' % username)
+ if self.rejected_users and username in self.rejected_users:
+ raise ValueError('%s is not an allowed user' % username)
+ if username == "anonymous" and password:
+ raise ValueError("can't assign password to anonymous user")
+ if not self.has_user(username):
+ raise ValueError('no such user %s' % username)
+
+ if username in self._dummy_authorizer.user_table:
+ # re-set parameters
+ del self._dummy_authorizer.user_table[username]
+ self._dummy_authorizer.add_user(username, password or "",
+ homedir or os.getcwd(),
+ perm or "",
+ msg_login or "",
+ msg_quit or "")
+ if homedir is None:
+ self._dummy_authorizer.user_table[username]['home'] = ""
+
+ def get_msg_login(self, username):
+ return self._get_key(username, 'msg_login') or self.msg_login
+
+ def get_msg_quit(self, username):
+ return self._get_key(username, 'msg_quit') or self.msg_quit
+
+ def get_perms(self, username):
+ overridden_perms = self._get_key(username, 'perm')
+ if overridden_perms:
+ return overridden_perms
+ if username == 'anonymous':
+ return 'elr'
+ return self.global_perm
+
+ def has_perm(self, username, perm, path=None):
+ return perm in self.get_perms(username)
+
+ def _get_key(self, username, key):
+ if self._dummy_authorizer.has_user(username):
+ return self._dummy_authorizer.user_table[username][key]
+
+ def _is_rejected_user(self, username):
+ """Return True if the user has been black listed via
+ allowed_users or rejected_users options.
+ """
+ if self.allowed_users and username not in self.allowed_users:
+ return True
+ if self.rejected_users and username in self.rejected_users:
+ return True
+ return False
+
+
+# Note: requires python >= 2.5
+try:
+ import pwd, spwd, crypt
+except ImportError:
+ pass
+else:
+ __all__.extend(['BaseUnixAuthorizer', 'UnixAuthorizer'])
+
+ # the uid/gid the server runs under
+ PROCESS_UID = os.getuid()
+ PROCESS_GID = os.getgid()
+
+ class BaseUnixAuthorizer(object):
+ """An authorizer compatible with Unix user account and password
+ database.
+ This class should not be used directly unless for subclassing.
+ Use higher-level UnixAuthorizer class instead.
+ """
+
+ def __init__(self, anonymous_user=None):
+ if os.geteuid() != 0 or not spwd.getspall():
+ raise AuthorizerError("super user privileges are required")
+ self.anonymous_user = anonymous_user
+
+ if self.anonymous_user is not None:
+ if not self.anonymous_user in self._get_system_users():
+ raise ValueError('no such user %s' % self.anonymous_user)
+ try:
+ pwd.getpwnam(self.anonymous_user).pw_dir
+ except KeyError:
+ raise ValueError('no such user %s' % anonymous_user)
+
+ # --- overridden / private API
+
+ def validate_authentication(self, username, password):
+ """Authenticates against shadow password db; return
+ True on success.
+ """
+ if username == "anonymous":
+ return self.anonymous_user is not None
+ try:
+ pw1 = spwd.getspnam(username).sp_pwd
+ pw2 = crypt.crypt(password, pw1)
+ except KeyError: # no such username
+ return False
+ else:
+ return pw1 == pw2
+
+ @replace_anonymous
+ def impersonate_user(self, username, password):
+ """Change process effective user/group ids to reflect
+ logged in user.
+ """
+ try:
+ pwdstruct = pwd.getpwnam(username)
+ except KeyError:
+ raise AuthorizerError('no such user %s' % username)
+ else:
+ os.setegid(pwdstruct.pw_gid)
+ os.seteuid(pwdstruct.pw_uid)
+
+ def terminate_impersonation(self, username):
+ """Revert process effective user/group IDs."""
+ os.setegid(PROCESS_GID)
+ os.seteuid(PROCESS_UID)
+
+ @replace_anonymous
+ def has_user(self, username):
+ """Return True if user exists on the Unix system.
+ If the user has been black listed via allowed_users or
+ rejected_users options always return False.
+ """
+ return username in self._get_system_users()
+
+ @replace_anonymous
+ def get_home_dir(self, username):
+ """Return user home directory."""
+ try:
+ return pwd.getpwnam(username).pw_dir
+ except KeyError:
+ raise AuthorizerError('no such user %s' % username)
+
+ @staticmethod
+ def _get_system_users():
+ """Return all users defined on the UNIX system."""
+ return [entry.pw_name for entry in pwd.getpwall()]
+
+ def get_msg_login(self, username):
+ return "Login successful."
+
+ def get_msg_quit(self, username):
+ return "Goodbye."
+
+ def get_perms(self, username):
+ return "elradfmw"
+
+ def has_perm(self, username, perm, path=None):
+ return perm in self.get_perms(username)
+
+
+ class UnixAuthorizer(_Base, BaseUnixAuthorizer):
+ """A wrapper on top of BaseUnixAuthorizer providing options
+ to specify what users should be allowed to login, per-user
+ options, etc.
+
+ Example usages:
+
+ >>> from pyftpdlib.contrib.authorizers import UnixAuthorizer
+ >>> # accept all except root
+ >>> auth = UnixAuthorizer(rejected_users=["root"])
+ >>>
+ >>> # accept some users only
+ >>> auth = UnixAuthorizer(allowed_users=["matt", "jay"])
+ >>>
+ >>> # accept everybody and don't care if they have not a valid shell
+ >>> auth = UnixAuthorizer(require_valid_shell=False)
+ >>>
+ >>> # set specific options for a user
+ >>> auth.override_user("matt", password="foo", perm="elr")
+ """
+
+ # --- public API
+
+ def __init__(self, global_perm="elradfmw",
+ allowed_users=[],
+ rejected_users=[],
+ require_valid_shell=True,
+ anonymous_user=None,
+ msg_login="Login successful.",
+ msg_quit="Goodbye."):
+ """Parameters:
+
+ - (string) global_perm:
+ a series of letters referencing the users permissions;
+ defaults to "elradfmw" which means full read and write
+ access for everybody (except anonymous).
+
+ - (list) allowed_users:
+ a list of users which are accepted for authenticating
+ against the FTP server; defaults to [] (no restrictions).
+
+ - (list) rejected_users:
+ a list of users which are not accepted for authenticating
+ against the FTP server; defaults to [] (no restrictions).
+
+ - (bool) require_valid_shell:
+ Deny access for those users which do not have a valid shell
+ binary listed in /etc/shells.
+ If /etc/shells cannot be found this is a no-op.
+ Anonymous user is not subject to this option, and is free
+ to not have a valid shell defined.
+ Defaults to True (a valid shell is required for login).
+
+ - (string) anonymous_user:
+ specify it if you intend to provide anonymous access.
+ The value expected is a string representing the system user
+ to use for managing anonymous sessions; defaults to None
+ (anonymous access disabled).
+
+ - (string) msg_login:
+ the string sent when client logs in.
+
+ - (string) msg_quit:
+ the string sent when client quits.
+ """
+ BaseUnixAuthorizer.__init__(self, anonymous_user)
+ self.global_perm = global_perm
+ self.allowed_users = allowed_users
+ self.rejected_users = rejected_users
+ self.anonymous_user = anonymous_user
+ self.require_valid_shell = require_valid_shell
+ self.msg_login = msg_login
+ self.msg_quit = msg_quit
+
+ self._dummy_authorizer = DummyAuthorizer()
+ self._dummy_authorizer._check_permissions('', global_perm)
+ _Base.__init__(self)
+ if require_valid_shell:
+ for username in self.allowed_users:
+ if not self._has_valid_shell(username):
+ raise ValueError("user %s has not a valid shell"
+ % username)
+
+ def override_user(self, username, password=None, homedir=None, perm=None,
+ msg_login=None, msg_quit=None):
+ """Overrides the options specified in the class constructor
+ for a specific user.
+ """
+ if self.require_valid_shell and username != 'anonymous':
+ if not self._has_valid_shell(username):
+ raise ValueError("user %s has not a valid shell"
+ % username)
+ _Base.override_user(self, username, password, homedir, perm,
+ msg_login, msg_quit)
+
+ # --- overridden / private API
+
+ def validate_authentication(self, username, password):
+ if username == "anonymous":
+ return self.anonymous_user is not None
+ if self._is_rejected_user(username):
+ return False
+ if self.require_valid_shell and username != 'anonymous':
+ if not self._has_valid_shell(username):
+ return False
+ overridden_password = self._get_key(username, 'pwd')
+ if overridden_password:
+ return overridden_password == password
+
+ return BaseUnixAuthorizer.validate_authentication(self, username, password)
+
+ @replace_anonymous
+ def has_user(self, username):
+ if self._is_rejected_user(username):
+ return False
+ return username in self._get_system_users()
+
+ @replace_anonymous
+ def get_home_dir(self, username):
+ overridden_home = self._get_key(username, 'home')
+ if overridden_home:
+ return overridden_home
+ return BaseUnixAuthorizer.get_home_dir(self, username)
+
+ @staticmethod
+ def _has_valid_shell(username):
+ """Return True if the user has a valid shell binary listed
+ in /etc/shells. If /etc/shells can't be found return True.
+ """
+ try:
+ file = open('/etc/shells', 'r')
+ except IOError, err:
+ if err.errno == errno.ENOENT:
+ return True
+ raise
+ else:
+ try:
+ try:
+ shell = pwd.getpwnam(username).pw_shell
+ except KeyError: # invalid user
+ return False
+ for line in file:
+ if line.startswith('#'):
+ continue
+ line = line.strip()
+ if line == shell:
+ return True
+ return False
+ finally:
+ file.close()
+
+
+# Note: requires pywin32 extension
+try:
+ import _winreg
+ import win32security, win32net, pywintypes, win32con, win32api
+except ImportError:
+ pass
+else:
+ __all__.extend(['BaseWindowsAuthorizer', 'WindowsAuthorizer'])
+
+ class BaseWindowsAuthorizer(object):
+ """An authorizer compatible with Windows user account and
+ password database.
+ This class should not be used directly unless for subclassing.
+ Use higher-level WinowsAuthorizer class instead.
+ """
+
+ def __init__(self, anonymous_user=None, anonymous_password=None):
+ # actually try to impersonate the user
+ self.anonymous_user = anonymous_user
+ self.anonymous_password = anonymous_password
+ if self.anonymous_user is not None:
+ self.impersonate_user(self.anonymous_user,
+ self.anonymous_password)
+ self.terminate_impersonation()
+
+ def validate_authentication(self, username, password):
+ if username == "anonymous":
+ return self.anonymous_user is not None
+ try:
+ win32security.LogonUser(username, None, password,
+ win32con.LOGON32_LOGON_INTERACTIVE,
+ win32con.LOGON32_PROVIDER_DEFAULT)
+ except pywintypes.error:
+ return False
+ else:
+ return True
+
+ @replace_anonymous
+ def impersonate_user(self, username, password):
+ """Impersonate the security context of another user."""
+ handler = win32security.LogonUser(username, None, password,
+ win32con.LOGON32_LOGON_INTERACTIVE,
+ win32con.LOGON32_PROVIDER_DEFAULT)
+ win32security.ImpersonateLoggedOnUser(handler)
+ handler.Close()
+
+ def terminate_impersonation(self, username):
+ """Terminate the impersonation of another user."""
+ win32security.RevertToSelf()
+
+ @replace_anonymous
+ def has_user(self, username):
+ return username in self._get_system_users()
+
+ @replace_anonymous
+ def get_home_dir(self, username):
+ """Return the user's profile directory, the closest thing
+ to a user home directory we have on Windows.
+ """
+ try:
+ sid = win32security.ConvertSidToStringSid(
+ win32security.LookupAccountName(None, username)[0])
+ except pywintypes.error, err:
+ raise AuthorizerError(err)
+ path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + \
+ "\\" + sid
+ try:
+ key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, path)
+ except WindowsError:
+ raise AuthorizerError("No profile directory defined for user %s"
+ % username)
+ value = _winreg.QueryValueEx(key, "ProfileImagePath")[0]
+ return win32api.ExpandEnvironmentStrings(value)
+
+ @classmethod
+ def _get_system_users(cls):
+ """Return all users defined on the Windows system."""
+ return [entry['name'] for entry in win32net.NetUserEnum(None, 0)[0]]
+
+ def get_msg_login(self, username):
+ return "Login successful."
+
+ def get_msg_quit(self, username):
+ return "Goodbye."
+
+ def get_perms(self, username):
+ return "elradfmw"
+
+ def has_perm(self, username, perm, path=None):
+ return perm in self.get_perms(username)
+
+
+ class WindowsAuthorizer(_Base, BaseWindowsAuthorizer):
+ """A wrapper on top of BaseWindowsAuthorizer providing options
+ to specify what users should be allowed to login, per-user
+ options, etc.
+
+ Example usages:
+
+ >>> from pyftpdlib.contrib.authorizers import WindowsAuthorizer
+ >>> # accept all except Administrator
+ >>> auth = UnixAuthorizer(rejected_users=["Administrator"])
+ >>>
+ >>> # accept some users only
+ >>> auth = UnixAuthorizer(allowed_users=["matt", "jay"])
+ >>>
+ >>> # set specific options for a user
+ >>> auth.override_user("matt", password="foo", perm="elr")
+ """
+
+ # --- public API
+
+ def __init__(self, global_perm="elradfmw",
+ allowed_users=[],
+ rejected_users=[],
+ anonymous_user=None,
+ anonymous_password=None,
+ msg_login="Login successful.",
+ msg_quit="Goodbye."):
+ """Parameters:
+
+ - (string) global_perm:
+ a series of letters referencing the users permissions;
+ defaults to "elradfmw" which means full read and write
+ access for everybody (except anonymous).
+
+ - (list) allowed_users:
+ a list of users which are accepted for authenticating
+ against the FTP server; defaults to [] (no restrictions).
+
+ - (list) rejected_users:
+ a list of users which are not accepted for authenticating
+ against the FTP server; defaults to [] (no restrictions).
+
+ - (string) anonymous_user:
+ specify it if you intend to provide anonymous access.
+ The value expected is a string representing the system user
+ to use for managing anonymous sessions.
+ As for IIS, it is recommended to use Guest account.
+ The common practice is to first enable the Guest user, which
+ is disabled by default and then assign an empty password.
+ Defaults to None (anonymous access disabled).
+
+ - (string) anonymous_password:
+ the password of the user who has been chosen to manage the
+ anonymous sessions. Defaults to None (empty password).
+
+ - (string) msg_login:
+ the string sent when client logs in.
+
+ - (string) msg_quit:
+ the string sent when client quits.
+ """
+ self.global_perm = global_perm
+ self.allowed_users = allowed_users
+ self.rejected_users = rejected_users
+ self.anonymous_user = anonymous_user
+ self.anonymous_password = anonymous_password
+ self.msg_login = msg_login
+ self.msg_quit = msg_quit
+ self._dummy_authorizer = DummyAuthorizer()
+ self._dummy_authorizer._check_permissions('', global_perm)
+ _Base.__init__(self)
+ # actually try to impersonate the user
+ if self.anonymous_user is not None:
+ self.impersonate_user(self.anonymous_user,
+ self.anonymous_password)
+ self.terminate_impersonation()
+
+ def override_user(self, username, password=None, homedir=None, perm=None,
+ msg_login=None, msg_quit=None):
+ """Overrides the options specified in the class constructor
+ for a specific user.
+ """
+ _Base.override_user(self, username, password, homedir, perm,
+ msg_login, msg_quit)
+
+ # --- overridden / private API
+
+ def validate_authentication(self, username, password):
+ """Authenticates against Windows user database; return
+ True on success.
+ """
+ if username == "anonymous":
+ return self.anonymous_user is not None
+ if self.allowed_users and username not in self.allowed_users:
+ return False
+ if self.rejected_users and username in self.rejected_users:
+ return False
+
+ overridden_password = self._get_key(username, 'pwd')
+ if overridden_password:
+ return overridden_password == password
+ else:
+ return BaseWindowsAuthorizer.validate_authentication(self,
+ username, password)
+
+ def impersonate_user(self, username, password):
+ """Impersonate the security context of another user."""
+ if username == "anonymous":
+ username = self.anonymous_user or ""
+ password = self.anonymous_password or ""
+ return BaseWindowsAuthorizer.impersonate_user(self, username, password)
+
+ @replace_anonymous
+ def has_user(self, username):
+ if self._is_rejected_user(username):
+ return False
+ return username in self._get_system_users()
+
+ @replace_anonymous
+ def get_home_dir(self, username):
+ overridden_home = self._get_key(username, 'home')
+ if overridden_home:
+ return overridden_home
+ return BaseWindowsAuthorizer.get_home_dir(self, username)
diff --git a/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/filesystems.py b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/filesystems.py
new file mode 100644
index 00000000000..b81b1b57565
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/filesystems.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+from pyftpdlib.ftpserver import AbstractedFS
+
+__all__ = ['UnixFilesystem']
+
+
+class UnixFilesystem(AbstractedFS):
+ """Represents the real UNIX filesystem.
+
+ Differently from AbstractedFS the client will login into
+ /home/<username> and will be able to escape its home directory
+ and navigate the real filesystem.
+ """
+
+ def __init__(self, root, cmd_channel):
+ AbstractedFS.__init__(self, root, cmd_channel)
+ # initial cwd was set to "/" to emulate a chroot jail
+ self.cwd = root
+
+ def ftp2fs(self, ftppath):
+ return self.ftpnorm(ftppath)
+
+ def fs2ftp(self, fspath):
+ return fspath
+
+ def validpath(self, path):
+ # validpath was used to check symlinks escaping user home
+ # directory; this is no longer necessary.
+ return True
diff --git a/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/handlers.py b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/handlers.py
new file mode 100644
index 00000000000..efc17bd21ef
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/pyftpdlib/contrib/handlers.py
@@ -0,0 +1,448 @@
+#!/usr/bin/env python
+# $Id$
+
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+
+"""This module is supposed to contain a series of classes which extend
+base pyftpdlib.ftpserver's FTPHandler and DTPHandler classes.
+
+As for now only one class is provided: TLS_FTPHandler.
+It is supposed to provide basic support for FTPS (FTP over SSL/TLS) as
+described in RFC-4217.
+
+Requires PyOpenSSL module (http://pypi.python.org/pypi/pyOpenSSL).
+For Python versions prior to 2.6 ssl module must be installed separately,
+see: http://pypi.python.org/pypi/ssl/
+
+Development status: experimental.
+"""
+
+
+import os
+import asyncore
+import socket
+import warnings
+import errno
+
+from pyftpdlib.ftpserver import FTPHandler, DTPHandler, proto_cmds, _DISCONNECTED
+
+__all__ = []
+
+# requires PyOpenSSL - http://pypi.python.org/pypi/pyOpenSSL
+try:
+ from OpenSSL import SSL
+except ImportError:
+ pass
+else:
+ __all__.extend(['SSLConnection', 'TLS_FTPHandler', 'TLS_DTPHandler'])
+
+
+ new_proto_cmds = proto_cmds.copy()
+ new_proto_cmds.update({
+ 'AUTH': dict(perm=None, auth=False, arg=True,
+ help='Syntax: AUTH <SP> TLS|SSL (set up secure control channel).'),
+ 'PBSZ': dict(perm=None, auth=False, arg=True,
+ help='Syntax: PBSZ <SP> 0 (negotiate TLS buffer).'),
+ 'PROT': dict(perm=None, auth=False, arg=True,
+ help='Syntax: PROT <SP> [C|P] (set up un/secure data channel).'),
+ })
+
+
+ class SSLConnection(object, asyncore.dispatcher):
+ """An asyncore.dispatcher subclass supporting TLS/SSL."""
+
+ _ssl_accepting = False
+ _ssl_established = False
+ _ssl_closing = False
+
+ def __init__(self, *args, **kwargs):
+ super(SSLConnection, self).__init__(*args, **kwargs)
+ self._error = False
+
+ def secure_connection(self, ssl_context):
+ """Secure the connection switching from plain-text to
+ SSL/TLS.
+ """
+ try:
+ self.socket = SSL.Connection(ssl_context, self.socket)
+ except socket.error:
+ self.close()
+ else:
+ self.socket.set_accept_state()
+ self._ssl_accepting = True
+
+ def _do_ssl_handshake(self):
+ self._ssl_accepting = True
+ try:
+ self.socket.do_handshake()
+ except (SSL.WantReadError, SSL.WantWriteError):
+ return
+ except SSL.SysCallError, (retval, desc):
+ if (retval == -1 and desc == 'Unexpected EOF') or retval > 0:
+ return self.handle_close()
+ raise
+ except SSL.Error:
+ return self.handle_failed_ssl_handshake()
+ else:
+ self._ssl_accepting = False
+ self._ssl_established = True
+ self.handle_ssl_established()
+
+ def handle_ssl_established(self):
+ """Called when SSL handshake has completed."""
+ pass
+
+ def handle_ssl_shutdown(self):
+ """Called when SSL shutdown() has completed."""
+ super(SSLConnection, self).close()
+
+ def handle_failed_ssl_handshake(self):
+ raise NotImplementedError("must be implemented in subclass")
+
+ def handle_read_event(self):
+ if self._ssl_accepting:
+ self._do_ssl_handshake()
+ elif self._ssl_closing:
+ self._do_ssl_shutdown()
+ else:
+ super(SSLConnection, self).handle_read_event()
+
+ def handle_write_event(self):
+ if self._ssl_accepting:
+ self._do_ssl_handshake()
+ elif self._ssl_closing:
+ self._do_ssl_shutdown()
+ else:
+ super(SSLConnection, self).handle_write_event()
+
+ def handle_error(self):
+ self._error = True
+ try:
+ raise
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except:
+ self.log_exception(self)
+ # when facing an unhandled exception in here it's better
+ # to rely on base class (FTPHandler or DTPHandler)
+ # close() method as it does not imply SSL shutdown logic
+ super(SSLConnection, self).close()
+
+ def send(self, data):
+ try:
+ return super(SSLConnection, self).send(data)
+ except (SSL.WantReadError, SSL.WantWriteError):
+ return 0
+ except SSL.ZeroReturnError:
+ super(SSLConnection, self).handle_close()
+ return 0
+ except SSL.SysCallError, (errnum, errstr):
+ if errnum == errno.EWOULDBLOCK:
+ return 0
+ elif errnum in _DISCONNECTED or errstr == 'Unexpected EOF':
+ super(SSLConnection, self).handle_close()
+ return 0
+ else:
+ raise
+
+ def recv(self, buffer_size):
+ try:
+ return super(SSLConnection, self).recv(buffer_size)
+ except (SSL.WantReadError, SSL.WantWriteError):
+ return ''
+ except SSL.ZeroReturnError:
+ super(SSLConnection, self).handle_close()
+ return ''
+ except SSL.SysCallError, (errnum, errstr):
+ if errnum in _DISCONNECTED or errstr == 'Unexpected EOF':
+ super(SSLConnection, self).handle_close()
+ return ''
+ else:
+ raise
+
+ def _do_ssl_shutdown(self):
+ """Executes a SSL_shutdown() call to revert the connection
+ back to clear-text.
+ twisted/internet/tcp.py code has been used as an example.
+ """
+ self._ssl_closing = True
+ # since SSL_shutdown() doesn't report errors, an empty
+ # write call is done first, to try to detect if the
+ # connection has gone away
+ try:
+ os.write(self.socket.fileno(), '')
+ except (OSError, socket.error), err:
+ if err.args[0] in (errno.EINTR, errno.EWOULDBLOCK, errno.ENOBUFS):
+ return
+ elif err.args[0] in _DISCONNECTED:
+ return super(SSLConnection, self).close()
+ else:
+ raise
+ # Ok, this a mess, but the underlying OpenSSL API simply
+ # *SUCKS* and I really couldn't do any better.
+ #
+ # Here we just want to shutdown() the SSL layer and then
+ # close() the connection so we're not interested in a
+ # complete SSL shutdown() handshake, so let's pretend
+ # we already received a "RECEIVED" shutdown notification
+ # from the client.
+ # Once the client received our "SENT" shutdown notification
+ # then we close() the connection.
+ #
+ # Since it is not clear what errors to expect during the
+ # entire procedure we catch them all and assume the
+ # following:
+ # - WantReadError and WantWriteError means "retry"
+ # - ZeroReturnError, SysCallError[EOF], Error[] are all
+ # aliases for disconnection
+ try:
+ laststate = self.socket.get_shutdown()
+ self.socket.set_shutdown(laststate | SSL.RECEIVED_SHUTDOWN)
+ done = self.socket.shutdown()
+ if not (laststate & SSL.RECEIVED_SHUTDOWN):
+ self.socket.set_shutdown(SSL.SENT_SHUTDOWN)
+ except (SSL.WantReadError, SSL.WantWriteError):
+ pass
+ except SSL.ZeroReturnError:
+ super(SSLConnection, self).close()
+ except SSL.SysCallError, (errnum, errstr):
+ if errnum in _DISCONNECTED or errstr == 'Unexpected EOF':
+ super(SSLConnection, self).close()
+ else:
+ raise
+ except SSL.Error, err:
+ # see:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=171
+ # https://bugs.launchpad.net/pyopenssl/+bug/785985
+ if err.args and not err.args[0]:
+ pass
+ else:
+ raise
+ except socket.error, err:
+ if err.args[0] in _DISCONNECTED:
+ super(SSLConnection, self).close()
+ else:
+ raise
+ else:
+ if done:
+ self._ssl_established = False
+ self._ssl_closing = False
+ self.handle_ssl_shutdown()
+
+ def close(self):
+ if self._ssl_established and not self._error:
+ self._do_ssl_shutdown()
+ else:
+ self._ssl_accepting = False
+ self._ssl_established = False
+ self._ssl_closing = False
+ super(SSLConnection, self).close()
+
+
+ class TLS_DTPHandler(SSLConnection, DTPHandler):
+ """A ftpserver.DTPHandler subclass supporting TLS/SSL."""
+
+ def __init__(self, sock_obj, cmd_channel):
+ super(TLS_DTPHandler, self).__init__(sock_obj, cmd_channel)
+ if self.cmd_channel._prot:
+ self.secure_connection(self.cmd_channel.ssl_context)
+
+ def _use_sendfile(self, producer):
+ return False
+
+ def handle_failed_ssl_handshake(self):
+ # TLS/SSL handshake failure, probably client's fault which
+ # used a SSL version different from server's.
+ # RFC-4217, chapter 10.2 expects us to return 522 over the
+ # command channel.
+ self.cmd_channel.respond("522 SSL handshake failed.")
+ self.cmd_channel.log_cmd("PROT", "P", 522, "SSL handshake failed.")
+ self.close()
+
+
+ class TLS_FTPHandler(SSLConnection, FTPHandler):
+ """A ftpserver.FTPHandler subclass supporting TLS/SSL.
+ Implements AUTH, PBSZ and PROT commands (RFC-2228 and RFC-4217).
+
+ Configurable attributes:
+
+ - (bool) tls_control_required:
+ When True requires SSL/TLS to be established on the control
+ channel, before logging in. This means the user will have
+ to issue AUTH before USER/PASS (default False).
+
+ - (bool) tls_data_required:
+ When True requires SSL/TLS to be established on the data
+ channel. This means the user will have to issue PROT
+ before PASV or PORT (default False).
+
+ SSL-specific options:
+
+ - (string) certfile:
+ the path to the file which contains a certificate to be
+ used to identify the local side of the connection.
+ This must always be specified, unless context is provided
+ instead.
+
+ - (string) keyfile:
+ the path to the file containing the private RSA key;
+ can be omittetted if certfile already contains the
+ private key (defaults: None).
+
+ - (int) protocol:
+ specifies which version of the SSL protocol to use when
+ establishing SSL/TLS sessions; clients can then only
+ connect using the configured protocol (defaults to SSLv23,
+ allowing SSLv3 and TLSv1 protocols).
+
+ Possible values:
+ * SSL.SSLv2_METHOD - allow only SSLv2
+ * SSL.SSLv3_METHOD - allow only SSLv3
+ * SSL.SSLv23_METHOD - allow both SSLv3 and TLSv1
+ * SSL.TLSv1_METHOD - allow only TLSv1
+
+ - (instance) context:
+ a SSL Context object previously configured; if specified
+ all other parameters will be ignored.
+ (default None).
+ """
+
+ # configurable attributes
+ tls_control_required = False
+ tls_data_required = False
+ certfile = None
+ keyfile = None
+ ssl_protocol = SSL.SSLv23_METHOD
+ ssl_context = None
+
+ # overridden attributes
+ proto_cmds = new_proto_cmds
+ dtp_handler = TLS_DTPHandler
+
+ def __init__(self, conn, server):
+ super(TLS_FTPHandler, self).__init__(conn, server)
+ if not self.connected:
+ return
+ self._extra_feats = ['AUTH TLS', 'AUTH SSL', 'PBSZ', 'PROT']
+ self._pbsz = False
+ self._prot = False
+ self.ssl_context = self.get_ssl_context()
+
+ @classmethod
+ def get_ssl_context(cls):
+ if cls.ssl_context is None:
+ if cls.certfile is None:
+ raise ValueError("at least certfile must be specified")
+ cls.ssl_context = SSL.Context(cls.ssl_protocol)
+ if cls.ssl_protocol != SSL.SSLv2_METHOD:
+ cls.ssl_context.set_options(SSL.OP_NO_SSLv2)
+ else:
+ warnings.warn("SSLv2 protocol is insecure", RuntimeWarning)
+ cls.ssl_context.use_certificate_file(cls.certfile)
+ if not cls.keyfile:
+ cls.keyfile = cls.certfile
+ cls.ssl_context.use_privatekey_file(cls.keyfile)
+ TLS_FTPHandler.ssl_context = cls.ssl_context
+ return cls.ssl_context
+
+ # --- overridden methods
+
+ def flush_account(self):
+ FTPHandler.flush_account(self)
+ self._pbsz = False
+ self._prot = False
+
+ def process_command(self, cmd, *args, **kwargs):
+ if cmd in ('USER', 'PASS'):
+ if self.tls_control_required and not self._ssl_established:
+ msg = "SSL/TLS required on the control channel."
+ self.respond("550 " + msg)
+ self.log_cmd(cmd, args[0], 550, msg)
+ return
+ elif cmd in ('PASV', 'EPSV', 'PORT', 'EPRT'):
+ if self.tls_data_required and not self._prot:
+ msg = "SSL/TLS required on the data channel."
+ self.respond("550 " + msg)
+ self.log_cmd(cmd, args[0], 550, msg)
+ return
+ FTPHandler.process_command(self, cmd, *args, **kwargs)
+
+ # --- new methods
+
+ def handle_failed_ssl_handshake(self):
+ # TLS/SSL handshake failure, probably client's fault which
+ # used a SSL version different from server's.
+ # We can't rely on the control connection anymore so we just
+ # disconnect the client without sending any response.
+ self.log("SSL handshake failed.")
+ self.close()
+
+ def ftp_AUTH(self, line):
+ """Set up secure control channel."""
+ arg = line.upper()
+ if isinstance(self.socket, SSL.Connection):
+ self.respond("503 Already using TLS.")
+ elif arg in ('TLS', 'TLS-C', 'SSL', 'TLS-P'):
+ # From RFC-4217: "As the SSL/TLS protocols self-negotiate
+ # their levels, there is no need to distinguish between SSL
+ # and TLS in the application layer".
+ self.respond('234 AUTH %s successful.' %arg)
+ self.secure_connection(self.ssl_context)
+ else:
+ self.respond("502 Unrecognized encryption type (use TLS or SSL).")
+
+ def ftp_PBSZ(self, line):
+ """Negotiate size of buffer for secure data transfer.
+ For TLS/SSL the only valid value for the parameter is '0'.
+ Any other value is accepted but ignored.
+ """
+ if not isinstance(self.socket, SSL.Connection):
+ self.respond("503 PBSZ not allowed on insecure control connection.")
+ else:
+ self.respond('200 PBSZ=0 successful.')
+ self._pbsz = True
+
+ def ftp_PROT(self, line):
+ """Setup un/secure data channel."""
+ arg = line.upper()
+ if not isinstance(self.socket, SSL.Connection):
+ self.respond("503 PROT not allowed on insecure control connection.")
+ elif not self._pbsz:
+ self.respond("503 You must issue the PBSZ command prior to PROT.")
+ elif arg == 'C':
+ self.respond('200 Protection set to Clear')
+ self._prot = False
+ elif arg == 'P':
+ self.respond('200 Protection set to Private')
+ self._prot = True
+ elif arg in ('S', 'E'):
+ self.respond('521 PROT %s unsupported (use C or P).' %arg)
+ else:
+ self.respond("502 Unrecognized PROT type (use C or P).")
diff --git a/chromium/third_party/pyftpdlib/src/pyftpdlib/ftpserver.py b/chromium/third_party/pyftpdlib/src/pyftpdlib/ftpserver.py
new file mode 100644
index 00000000000..33a54d38a1f
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/pyftpdlib/ftpserver.py
@@ -0,0 +1,3966 @@
+#!/usr/bin/env python
+# $Id$
+
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+
+"""pyftpdlib: RFC-959 asynchronous FTP server.
+
+pyftpdlib implements a fully functioning asynchronous FTP server as
+defined in RFC-959. A hierarchy of classes outlined below implement
+the backend functionality for the FTPd:
+
+ [FTPServer] - the base class for the backend.
+
+ [FTPHandler] - a class representing the server-protocol-interpreter
+ (server-PI, see RFC-959). Each time a new connection occurs
+ FTPServer will create a new FTPHandler instance to handle the
+ current PI session.
+
+ [ActiveDTP], [PassiveDTP] - base classes for active/passive-DTP
+ backends.
+
+ [DTPHandler] - this class handles processing of data transfer
+ operations (server-DTP, see RFC-959).
+
+ [ThrottledDTPHandler] - a DTPHandler subclass implementing transfer
+ rates limits.
+
+ [DummyAuthorizer] - an "authorizer" is a class handling FTPd
+ authentications and permissions. It is used inside FTPHandler class
+ to verify user passwords, to get user's home directory and to get
+ permissions when a filesystem read/write occurs. "DummyAuthorizer"
+ is the base authorizer class providing a platform independent
+ interface for managing virtual users.
+
+ [AbstractedFS] - class used to interact with the file system,
+ providing a high level, cross-platform interface compatible
+ with both Windows and UNIX style filesystems.
+
+ [CallLater] - calls a function at a later time whithin the polling
+ loop asynchronously.
+
+ [AuthorizerError] - base class for authorizers exceptions.
+
+
+pyftpdlib also provides 3 different logging streams through 3 functions
+which can be overridden to allow for custom logging.
+
+ [log] - the main logger that logs the most important messages for
+ the end user regarding the FTPd.
+
+ [logline] - this function is used to log commands and responses
+ passing through the control FTP channel.
+
+ [logerror] - log traceback outputs occurring in case of errors.
+
+
+Usage example:
+
+>>> from pyftpdlib import ftpserver
+>>> authorizer = ftpserver.DummyAuthorizer()
+>>> authorizer.add_user('user', 'password', '/home/user', perm='elradfmw')
+>>> authorizer.add_anonymous('/home/nobody')
+>>> ftp_handler = ftpserver.FTPHandler
+>>> ftp_handler.authorizer = authorizer
+>>> address = ("127.0.0.1", 21)
+>>> ftpd = ftpserver.FTPServer(address, ftp_handler)
+>>> ftpd.serve_forever()
+Serving FTP on 127.0.0.1:21
+[]127.0.0.1:2503 connected.
+127.0.0.1:2503 ==> 220 Ready.
+127.0.0.1:2503 <== USER anonymous
+127.0.0.1:2503 ==> 331 Username ok, send password.
+127.0.0.1:2503 <== PASS ******
+127.0.0.1:2503 ==> 230 Login successful.
+[anonymous]@127.0.0.1:2503 User anonymous logged in.
+127.0.0.1:2503 <== TYPE A
+127.0.0.1:2503 ==> 200 Type set to: ASCII.
+127.0.0.1:2503 <== PASV
+127.0.0.1:2503 ==> 227 Entering passive mode (127,0,0,1,9,201).
+127.0.0.1:2503 <== LIST
+127.0.0.1:2503 ==> 150 File status okay. About to open data connection.
+[anonymous]@127.0.0.1:2503 OK LIST "/". Transfer starting.
+127.0.0.1:2503 ==> 226 Transfer complete.
+[anonymous]@127.0.0.1:2503 Transfer complete. 706 bytes transmitted.
+127.0.0.1:2503 <== QUIT
+127.0.0.1:2503 ==> 221 Goodbye.
+[anonymous]@127.0.0.1:2503 Disconnected.
+"""
+
+
+import asyncore
+import asynchat
+import socket
+import os
+import sys
+import traceback
+import errno
+import time
+import glob
+import tempfile
+import warnings
+import random
+import stat
+import heapq
+import optparse
+from tarfile import filemode as _filemode
+
+try:
+ import pwd
+ import grp
+except ImportError:
+ pwd = grp = None
+
+# http://code.google.com/p/pysendfile/
+try:
+ from sendfile import sendfile
+except ImportError:
+ sendfile = None
+
+
+__all__ = ['proto_cmds', 'Error', 'log', 'logline', 'logerror', 'DummyAuthorizer',
+ 'AuthorizerError', 'FTPHandler', 'FTPServer', 'PassiveDTP',
+ 'ActiveDTP', 'DTPHandler', 'ThrottledDTPHandler', 'FileProducer',
+ 'BufferedIteratorProducer', 'AbstractedFS', 'CallLater', 'CallEvery']
+
+
+__pname__ = 'Python FTP server library (pyftpdlib)'
+__ver__ = '0.7.0'
+__date__ = 'XXXX-XX-XX'
+__author__ = "Giampaolo Rodola' <g.rodola@gmail.com>"
+__web__ = 'http://code.google.com/p/pyftpdlib/'
+
+
+_DISCONNECTED = frozenset((errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN,
+ errno.ECONNABORTED, errno.EPIPE, errno.EBADF))
+
+proto_cmds = {
+ 'ABOR' : dict(perm=None, auth=True, arg=False,
+ help='Syntax: ABOR (abort transfer).'),
+ 'ALLO' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: ALLO <SP> bytes (noop; allocate storage).'),
+ 'APPE' : dict(perm='a', auth=True, arg=True,
+ help='Syntax: APPE <SP> file-name (append data to file).'),
+ 'CDUP' : dict(perm='e', auth=True, arg=False,
+ help='Syntax: CDUP (go to parent directory).'),
+ 'CWD' : dict(perm='e', auth=True, arg=None,
+ help='Syntax: CWD [<SP> dir-name] (change working directory).'),
+ 'DELE' : dict(perm='d', auth=True, arg=True,
+ help='Syntax: DELE <SP> file-name (delete file).'),
+ 'EPRT' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: EPRT <SP> |proto|ip|port| (extended active mode).'),
+ 'EPSV' : dict(perm=None, auth=True, arg=None,
+ help='Syntax: EPSV [<SP> proto/"ALL"] (extended passive mode).'),
+ 'FEAT' : dict(perm=None, auth=False, arg=False,
+ help='Syntax: FEAT (list all new features supported).'),
+ 'HELP' : dict(perm=None, auth=False, arg=None,
+ help='Syntax: HELP [<SP> cmd] (show help).'),
+ 'LIST' : dict(perm='l', auth=True, arg=None,
+ help='Syntax: LIST [<SP> path] (list files).'),
+ 'MDTM' : dict(perm='l', auth=True, arg=True,
+ help='Syntax: MDTM [<SP> path] (file last modification time).'),
+ 'MLSD' : dict(perm='l', auth=True, arg=None,
+ help='Syntax: MLSD [<SP> path] (list directory).'),
+ 'MLST' : dict(perm='l', auth=True, arg=None,
+ help='Syntax: MLST [<SP> path] (show information about path).'),
+ 'MODE' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: MODE <SP> mode (noop; set data transfer mode).'),
+ 'MKD' : dict(perm='m', auth=True, arg=True,
+ help='Syntax: MKD <SP> path (create directory).'),
+ 'NLST' : dict(perm='l', auth=True, arg=None,
+ help='Syntax: NLST [<SP> path] (list path in a compact form).'),
+ 'NOOP' : dict(perm=None, auth=False, arg=False,
+ help='Syntax: NOOP (just do nothing).'),
+ 'OPTS' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: OPTS <SP> cmd [<SP> option] (set option for command).'),
+ 'PASS' : dict(perm=None, auth=False, arg=True,
+ help='Syntax: PASS <SP> password (set user password).'),
+ 'PASV' : dict(perm=None, auth=True, arg=False,
+ help='Syntax: PASV (open passive data connection).'),
+ 'PORT' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: PORT <sp> h1,h2,h3,h4,p1,p2 (open active data connection).'),
+ 'PWD' : dict(perm=None, auth=True, arg=False,
+ help='Syntax: PWD (get current working directory).'),
+ 'QUIT' : dict(perm=None, auth=False, arg=False,
+ help='Syntax: QUIT (quit current session).'),
+ 'REIN' : dict(perm=None, auth=True, arg=False,
+ help='Syntax: REIN (flush account).'),
+ 'REST' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: REST <SP> offset (set file offset).'),
+ 'RETR' : dict(perm='r', auth=True, arg=True,
+ help='Syntax: RETR <SP> file-name (retrieve a file).'),
+ 'RMD' : dict(perm='d', auth=True, arg=True,
+ help='Syntax: RMD <SP> dir-name (remove directory).'),
+ 'RNFR' : dict(perm='f', auth=True, arg=True,
+ help='Syntax: RNFR <SP> file-name (rename (source name)).'),
+ 'RNTO' : dict(perm='f', auth=True, arg=True,
+ help='Syntax: RNTO <SP> file-name (rename (destination name)).'),
+ 'SITE' : dict(perm=None, auth=False, arg=True,
+ help='Syntax: SITE <SP> site-command (execute SITE command).'),
+ 'SITE HELP' : dict(perm=None, auth=False, arg=None,
+ help='Syntax: SITE HELP [<SP> site-command] (show SITE command help).'),
+ 'SITE CHMOD': dict(perm='M', auth=True, arg=True,
+ help='Syntax: SITE CHMOD <SP> mode path (change file mode).'),
+ 'SIZE' : dict(perm='l', auth=True, arg=True,
+ help='Syntax: SIZE <SP> file-name (get file size).'),
+ 'STAT' : dict(perm='l', auth=False, arg=None,
+ help='Syntax: STAT [<SP> path name] (server stats [list files]).'),
+ 'STOR' : dict(perm='w', auth=True, arg=True,
+ help='Syntax: STOR <SP> file-name (store a file).'),
+ 'STOU' : dict(perm='w', auth=True, arg=None,
+ help='Syntax: STOU [<SP> file-name] (store a file with a unique name).'),
+ 'STRU' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: STRU <SP> type (noop; set file structure).'),
+ 'SYST' : dict(perm=None, auth=False, arg=False,
+ help='Syntax: SYST (get operating system type).'),
+ 'TYPE' : dict(perm=None, auth=True, arg=True,
+ help='Syntax: TYPE <SP> [A | I] (set transfer type).'),
+ 'USER' : dict(perm=None, auth=False, arg=True,
+ help='Syntax: USER <SP> user-name (set username).'),
+ 'XCUP' : dict(perm='e', auth=True, arg=False,
+ help='Syntax: XCUP (obsolete; go to parent directory).'),
+ 'XCWD' : dict(perm='e', auth=True, arg=None,
+ help='Syntax: XCWD [<SP> dir-name] (obsolete; change directory).'),
+ 'XMKD' : dict(perm='m', auth=True, arg=True,
+ help='Syntax: XMKD <SP> dir-name (obsolete; create directory).'),
+ 'XPWD' : dict(perm=None, auth=True, arg=False,
+ help='Syntax: XPWD (obsolete; get current dir).'),
+ 'XRMD' : dict(perm='d', auth=True, arg=True,
+ help='Syntax: XRMD <SP> dir-name (obsolete; remove directory).'),
+ }
+
+if not hasattr(os, 'chmod'):
+ del proto_cmds['SITE CHMOD']
+
+
+# A wrapper around os.strerror() which may be not available
+# on all platforms (e.g. pythonCE). Expected arg is a
+# EnvironmentError or derived class instance.
+if hasattr(os, 'strerror'):
+ _strerror = lambda err: os.strerror(err.errno)
+else:
+ _strerror = lambda err: err.strerror
+
+
+class _Scheduler(object):
+ """Run the scheduled functions due to expire soonest (if any)."""
+
+ def __init__(self):
+ # the heap used for the scheduled tasks
+ self._tasks = []
+ self._cancellations = 0
+
+ def __call__(self):
+ now = time.time()
+ calls = []
+ while self._tasks:
+ if now < self._tasks[0].timeout:
+ break
+ call = heapq.heappop(self._tasks)
+ if not call.cancelled:
+ calls.append(call)
+ else:
+ self._cancellations -= 1
+
+ for call in calls:
+ if call._repush:
+ heapq.heappush(self._tasks, call)
+ call._repush = False
+ continue
+ try:
+ call.call()
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except:
+ logerror(traceback.format_exc())
+
+ # remove cancelled tasks and re-heapify the queue if the
+ # number of cancelled tasks is more than the half of the
+ # entire queue
+ if self._cancellations > 512 \
+ and self._cancellations > (len(self._tasks) >> 1):
+ self._cancellations = 0
+ self._tasks = [x for x in self._tasks if not x.cancelled]
+ self.reheapify()
+
+ def register(self, what):
+ heapq.heappush(self._tasks, what)
+
+ def unregister(self, what):
+ self._cancellations += 1
+
+ def reheapify(self):
+ heapq.heapify(self._tasks)
+
+
+_scheduler = _Scheduler()
+
+# dirty hack to support property.setter on python < 2.6
+if not hasattr(property, "setter"):
+ class property(property):
+ def setter(self, value):
+ cls_ns = sys._getframe(1).f_locals
+ for k, v in cls_ns.iteritems():
+ if v == self:
+ name = k
+ break
+ cls_ns[name] = property(self.fget, value, self.fdel, self.__doc__)
+ return cls_ns[name]
+
+_months_map = {1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul',
+ 8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
+
+
+class CallLater(object):
+ """Calls a function at a later time.
+
+ It can be used to asynchronously schedule a call within the polling
+ loop without blocking it. The instance returned is an object that
+ can be used to cancel or reschedule the call.
+ """
+ __slots__ = ('_delay', '_target', '_args', '_kwargs', '_errback',
+ '_repush', 'timeout', 'cancelled')
+
+ def __init__(self, seconds, target, *args, **kwargs):
+ """
+ - (int) seconds: the number of seconds to wait
+ - (obj) target: the callable object to call later
+ - args: the arguments to call it with
+ - kwargs: the keyword arguments to call it with; a special
+ '_errback' parameter can be passed: it is a callable
+ called in case target function raises an exception.
+ """
+ assert callable(target), "%s is not callable" % target
+ assert sys.maxint >= seconds >= 0, "%s is not greater than or equal " \
+ "to 0 seconds" % seconds
+ self._delay = seconds
+ self._target = target
+ self._args = args
+ self._kwargs = kwargs
+ self._errback = kwargs.pop('_errback', None)
+ self._repush = False
+ # seconds from the epoch at which to call the function
+ self.timeout = time.time() + self._delay
+ self.cancelled = False
+ _scheduler.register(self)
+
+ def __lt__(self, other):
+ return self.timeout < other.timeout
+
+ def __le__(self, other):
+ return self.timeout <= other.timeout
+
+ def _post_call(self, exc):
+ if not self.cancelled:
+ self.cancel()
+
+ def call(self):
+ """Call this scheduled function."""
+ assert not self.cancelled, "Already cancelled"
+ exc = None
+ try:
+ try:
+ self._target(*self._args, **self._kwargs)
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except Exception, exc:
+ if self._errback is not None:
+ self._errback()
+ else:
+ raise
+ finally:
+ self._post_call(exc)
+
+ def reset(self):
+ """Reschedule this call resetting the current countdown."""
+ assert not self.cancelled, "Already cancelled"
+ self.timeout = time.time() + self._delay
+ self._repush = True
+
+ def delay(self, seconds):
+ """Reschedule this call for a later time."""
+ assert not self.cancelled, "Already cancelled."
+ assert sys.maxint >= seconds >= 0, "%s is not greater than or equal " \
+ "to 0 seconds" % seconds
+ self._delay = seconds
+ newtime = time.time() + self._delay
+ if newtime > self.timeout:
+ self.timeout = newtime
+ self._repush = True
+ else:
+ # XXX - slow, can be improved
+ self.timeout = newtime
+ _scheduler.reheapify()
+
+ def cancel(self):
+ """Unschedule this call."""
+ assert not self.cancelled, "Already cancelled"
+ self.cancelled = True
+ del self._target, self._args, self._kwargs, self._errback
+ _scheduler.unregister(self)
+
+
+class CallEvery(CallLater):
+ """Calls a function every x seconds.
+ It accepts the same arguments as CallLater and shares the same API.
+ """
+
+ def _post_call(self, exc):
+ if not self.cancelled:
+ if exc:
+ self.cancel()
+ else:
+ self.timeout = time.time() + self._delay
+ _scheduler.register(self)
+
+
+# --- library defined exceptions
+
+class Error(Exception):
+ """Base class for module exceptions."""
+
+class AuthorizerError(Error):
+ """Base class for authorizer exceptions."""
+
+class _FileReadWriteError(OSError):
+ """Exception raised when reading or writing a file during a transfer."""
+
+
+# --- loggers
+
+def log(msg):
+ """Log messages intended for the end user."""
+ print msg
+
+def logline(msg):
+ """Log commands and responses passing through the command channel."""
+ print msg
+
+def logerror(msg):
+ """Log traceback outputs occurring in case of errors."""
+ sys.stderr.write(str(msg) + '\n')
+ sys.stderr.flush()
+
+
+# --- authorizers
+
+class DummyAuthorizer(object):
+ """Basic "dummy" authorizer class, suitable for subclassing to
+ create your own custom authorizers.
+
+ An "authorizer" is a class handling authentications and permissions
+ of the FTP server. It is used inside FTPHandler class for verifying
+ user's password, getting users home directory, checking user
+ permissions when a file read/write event occurs and changing user
+ before accessing the filesystem.
+
+ DummyAuthorizer is the base authorizer, providing a platform
+ independent interface for managing "virtual" FTP users. System
+ dependent authorizers can by written by subclassing this base
+ class and overriding appropriate methods as necessary.
+ """
+
+ read_perms = "elr"
+ write_perms = "adfmwM"
+
+ def __init__(self):
+ self.user_table = {}
+
+ def add_user(self, username, password, homedir, perm='elr',
+ msg_login="Login successful.", msg_quit="Goodbye."):
+ """Add a user to the virtual users table.
+
+ AuthorizerError exceptions raised on error conditions such as
+ invalid permissions, missing home directory or duplicate usernames.
+
+ Optional perm argument is a string referencing the user's
+ permissions explained below:
+
+ Read permissions:
+ - "e" = change directory (CWD command)
+ - "l" = list files (LIST, NLST, STAT, MLSD, MLST, SIZE, MDTM commands)
+ - "r" = retrieve file from the server (RETR command)
+
+ Write permissions:
+ - "a" = append data to an existing file (APPE command)
+ - "d" = delete file or directory (DELE, RMD commands)
+ - "f" = rename file or directory (RNFR, RNTO commands)
+ - "m" = create directory (MKD command)
+ - "w" = store a file to the server (STOR, STOU commands)
+ - "M" = change file mode (SITE CHMOD command)
+
+ Optional msg_login and msg_quit arguments can be specified to
+ provide customized response strings when user log-in and quit.
+ """
+ if self.has_user(username):
+ raise ValueError('user "%s" already exists' % username)
+ if not os.path.isdir(homedir):
+ raise ValueError('no such directory: "%s"' % homedir)
+ homedir = os.path.realpath(homedir)
+ self._check_permissions(username, perm)
+ dic = {'pwd': str(password),
+ 'home': homedir,
+ 'perm': perm,
+ 'operms': {},
+ 'msg_login': str(msg_login),
+ 'msg_quit': str(msg_quit)
+ }
+ self.user_table[username] = dic
+
+ def add_anonymous(self, homedir, **kwargs):
+ """Add an anonymous user to the virtual users table.
+
+ AuthorizerError exception raised on error conditions such as
+ invalid permissions, missing home directory, or duplicate
+ anonymous users.
+
+ The keyword arguments in kwargs are the same expected by
+ add_user method: "perm", "msg_login" and "msg_quit".
+
+ The optional "perm" keyword argument is a string defaulting to
+ "elr" referencing "read-only" anonymous user's permissions.
+
+ Using write permission values ("adfmwM") results in a
+ RuntimeWarning.
+ """
+ DummyAuthorizer.add_user(self, 'anonymous', '', homedir, **kwargs)
+
+ def remove_user(self, username):
+ """Remove a user from the virtual users table."""
+ del self.user_table[username]
+
+ def override_perm(self, username, directory, perm, recursive=False):
+ """Override permissions for a given directory."""
+ self._check_permissions(username, perm)
+ if not os.path.isdir(directory):
+ raise ValueError('no such directory: "%s"' % directory)
+ directory = os.path.normcase(os.path.realpath(directory))
+ home = os.path.normcase(self.get_home_dir(username))
+ if directory == home:
+ raise ValueError("can't override home directory permissions")
+ if not self._issubpath(directory, home):
+ raise ValueError("path escapes user home directory")
+ self.user_table[username]['operms'][directory] = perm, recursive
+
+ def validate_authentication(self, username, password):
+ """Return True if the supplied username and password match the
+ stored credentials."""
+ if not self.has_user(username):
+ return False
+ if username == 'anonymous':
+ return True
+ return self.user_table[username]['pwd'] == password
+
+ def impersonate_user(self, username, password):
+ """Impersonate another user (noop).
+
+ It is always called before accessing the filesystem.
+ By default it does nothing. The subclass overriding this
+ method is expected to provide a mechanism to change the
+ current user.
+ """
+
+ def terminate_impersonation(self, username):
+ """Terminate impersonation (noop).
+
+ It is always called after having accessed the filesystem.
+ By default it does nothing. The subclass overriding this
+ method is expected to provide a mechanism to switch back
+ to the original user.
+ """
+
+ def has_user(self, username):
+ """Whether the username exists in the virtual users table."""
+ return username in self.user_table
+
+ def has_perm(self, username, perm, path=None):
+ """Whether the user has permission over path (an absolute
+ pathname of a file or a directory).
+
+ Expected perm argument is one of the following letters:
+ "elradfmwM".
+ """
+ if path is None:
+ return perm in self.user_table[username]['perm']
+
+ path = os.path.normcase(path)
+ for dir in self.user_table[username]['operms'].keys():
+ operm, recursive = self.user_table[username]['operms'][dir]
+ if self._issubpath(path, dir):
+ if recursive:
+ return perm in operm
+ if (path == dir) or (os.path.dirname(path) == dir \
+ and not os.path.isdir(path)):
+ return perm in operm
+
+ return perm in self.user_table[username]['perm']
+
+ def get_perms(self, username):
+ """Return current user permissions."""
+ return self.user_table[username]['perm']
+
+ def get_home_dir(self, username):
+ """Return the user's home directory."""
+ return self.user_table[username]['home']
+
+ def get_msg_login(self, username):
+ """Return the user's login message."""
+ return self.user_table[username]['msg_login']
+
+ def get_msg_quit(self, username):
+ """Return the user's quitting message."""
+ return self.user_table[username]['msg_quit']
+
+ def _check_permissions(self, username, perm):
+ warned = 0
+ for p in perm:
+ if p not in self.read_perms + self.write_perms:
+ raise ValueError('no such permission "%s"' % p)
+ if (username == 'anonymous') and (p in self.write_perms) and not warned:
+ warnings.warn("write permissions assigned to anonymous user.",
+ RuntimeWarning)
+ warned = 1
+
+ def _issubpath(self, a, b):
+ """Return True if a is a sub-path of b or if the paths are equal."""
+ p1 = a.rstrip(os.sep).split(os.sep)
+ p2 = b.rstrip(os.sep).split(os.sep)
+ return p1[:len(p2)] == p2
+
+
+
+# --- DTP classes
+
+class PassiveDTP(object, asyncore.dispatcher):
+ """This class is an asyncore.dispatcher subclass. It creates a
+ socket listening on a local port, dispatching the resultant
+ connection to DTPHandler.
+
+ - (int) timeout: the timeout for a remote client to establish
+ connection with the listening socket. Defaults to 30 seconds.
+ """
+ timeout = 30
+
+ def __init__(self, cmd_channel, extmode=False):
+ """Initialize the passive data server.
+
+ - (instance) cmd_channel: the command channel class instance.
+ - (bool) extmode: wheter use extended passive mode response type.
+ """
+ self.cmd_channel = cmd_channel
+ self.log = cmd_channel.log
+ self.log_exception = cmd_channel.log_exception
+ self._closed = False
+ asyncore.dispatcher.__init__(self)
+ if self.timeout:
+ self._idler = CallLater(self.timeout, self.handle_timeout,
+ _errback=self.handle_error)
+ else:
+ self._idler = None
+
+ local_ip = self.cmd_channel.socket.getsockname()[0]
+ if local_ip in self.cmd_channel.masquerade_address_map:
+ masqueraded_ip = self.cmd_channel.masquerade_address_map[local_ip]
+ elif self.cmd_channel.masquerade_address:
+ masqueraded_ip = self.cmd_channel.masquerade_address
+ else:
+ masqueraded_ip = None
+
+ self.create_socket(self.cmd_channel._af, socket.SOCK_STREAM)
+
+ if self.cmd_channel.passive_ports is None:
+ # By using 0 as port number value we let kernel choose a
+ # free unprivileged random port.
+ self.bind((local_ip, 0))
+ else:
+ ports = list(self.cmd_channel.passive_ports)
+ while ports:
+ port = ports.pop(random.randint(0, len(ports) -1))
+ self.set_reuse_addr()
+ try:
+ self.bind((local_ip, port))
+ except socket.error, err:
+ if err.args[0] == errno.EADDRINUSE: # port already in use
+ if ports:
+ continue
+ # If cannot use one of the ports in the configured
+ # range we'll use a kernel-assigned port, and log
+ # a message reporting the issue.
+ # By using 0 as port number value we let kernel
+ # choose a free unprivileged random port.
+ else:
+ self.bind((local_ip, 0))
+ self.log(
+ "Can't find a valid passive port in the "
+ "configured range. A random kernel-assigned "
+ "port will be used."
+ )
+ else:
+ raise
+ else:
+ break
+ self.listen(5)
+
+ port = self.socket.getsockname()[1]
+ if not extmode:
+ ip = masqueraded_ip or local_ip
+ if ip.startswith('::ffff:'):
+ # In this scenario, the server has an IPv6 socket, but
+ # the remote client is using IPv4 and its address is
+ # represented as an IPv4-mapped IPv6 address which
+ # looks like this ::ffff:151.12.5.65, see:
+ # http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_addresses
+ # http://tools.ietf.org/html/rfc3493.html#section-3.7
+ # We truncate the first bytes to make it look like a
+ # common IPv4 address.
+ ip = ip[7:]
+ # The format of 227 response in not standardized.
+ # This is the most expected:
+ 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)
+
+ def set_reuse_addr(self):
+ # overridden for convenience; avoid to reuse address on Windows
+ if (os.name in ('nt', 'ce')) or (sys.platform == 'cygwin'):
+ return
+ asyncore.dispatcher.set_reuse_addr(self)
+
+ # --- connection / overridden
+
+ def handle_accept(self):
+ """Called when remote client initiates a connection."""
+ if not self.cmd_channel.connected:
+ return self.close()
+ try:
+ sock, addr = self.accept()
+ except TypeError:
+ # sometimes accept() might return None (see issue 91)
+ return
+ except socket.error, err:
+ # ECONNABORTED might be thrown on *BSD (see issue 105)
+ if err.args[0] != errno.ECONNABORTED:
+ self.log_exception(self)
+ return
+ else:
+ # sometimes addr == None instead of (ip, port) (see issue 104)
+ if addr == None:
+ return
+
+ # Check the origin of data connection. If not expressively
+ # configured we drop the incoming data connection if remote
+ # IP address does not match the client's IP address.
+ if self.cmd_channel.remote_ip != addr[0]:
+ if not self.cmd_channel.permit_foreign_addresses:
+ try:
+ sock.close()
+ except socket.error:
+ pass
+ msg = 'Rejected data connection from foreign address %s:%s.' \
+ %(addr[0], addr[1])
+ self.cmd_channel.respond("425 %s" % msg)
+ self.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])
+ self.log(msg)
+ # Immediately close the current channel (we accept only one
+ # connection at time) and avoid running out of max connections
+ # limit.
+ self.close()
+ # delegate such connection to DTP handler
+ if self.cmd_channel.connected:
+ handler = self.cmd_channel.dtp_handler(sock, self.cmd_channel)
+ if handler.connected:
+ self.cmd_channel.data_channel = handler
+ self.cmd_channel._on_dtp_connection()
+
+ def handle_timeout(self):
+ if self.cmd_channel.connected:
+ self.cmd_channel.respond("421 Passive data channel timed out.")
+ self.close()
+
+ def writable(self):
+ return 0
+
+ def handle_error(self):
+ """Called to handle any uncaught exceptions."""
+ try:
+ raise
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except:
+ logerror(traceback.format_exc())
+ self.close()
+
+ def close(self):
+ if not self._closed:
+ self._closed = True
+ asyncore.dispatcher.close(self)
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.cancel()
+
+
+class ActiveDTP(object, asyncore.dispatcher):
+ """This class is an asyncore.disptacher subclass. It creates a
+ socket resulting from the connection to a remote user-port,
+ dispatching it to DTPHandler.
+
+ - (int) timeout: the timeout for us to establish connection with
+ the client's listening data socket.
+ """
+ timeout = 30
+
+ def __init__(self, ip, port, cmd_channel):
+ """Initialize the active data channel attemping to connect
+ to remote data socket.
+
+ - (str) ip: the remote IP address.
+ - (int) port: the remote port.
+ - (instance) cmd_channel: the command channel class instance.
+ """
+ self.cmd_channel = cmd_channel
+ self.log = cmd_channel.log
+ self.log_exception = cmd_channel.log_exception
+ self._closed = True
+ asyncore.dispatcher.__init__(self)
+ if self.timeout:
+ self._idler = CallLater(self.timeout, self.handle_timeout,
+ _errback=self.handle_error)
+ else:
+ self._idler = None
+ if ip.count('.') == 4:
+ self._cmd = "PORT"
+ self._normalized_addr = "%s:%s" % (ip, port)
+ else:
+ self._cmd = "EPRT"
+ self._normalized_addr = "[%s]:%s" % (ip, port)
+
+ self.create_socket(self.cmd_channel._af, socket.SOCK_STREAM)
+ # Have the active connection come from the same IP address
+ # as the command channel, see:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=123
+ source_ip = self.cmd_channel.socket.getsockname()[0]
+ self.bind((source_ip, 0))
+ try:
+ self.connect((ip, port))
+ except (socket.gaierror, socket.error):
+ self.handle_expt()
+
+ # overridden to prevent unhandled read/write event messages to
+ # be printed by asyncore on Python < 2.6
+
+ def handle_write(self):
+ pass
+
+ def handle_read(self):
+ pass
+
+ def handle_connect(self):
+ """Called when connection is established."""
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.cancel()
+ if not self.cmd_channel.connected:
+ return self.close()
+ # fix for asyncore on python < 2.6, meaning we aren't
+ # actually connected.
+ # test_active_conn_error tests this condition
+ err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if err != 0:
+ raise socket.error(err)
+ #
+ msg = 'Active data connection established.'
+ self.cmd_channel.respond('200 ' + msg)
+ self.cmd_channel.log_cmd(self._cmd, self._normalized_addr, 200, msg)
+ #
+ if not self.cmd_channel.connected:
+ return self.close()
+ # delegate such connection to DTP handler
+ handler = self.cmd_channel.dtp_handler(self.socket, self.cmd_channel)
+ self.cmd_channel.data_channel = handler
+ self.cmd_channel._on_dtp_connection()
+ # Can't close right now as the handler would have the socket
+ # object disconnected. This class will be "closed" once the
+ # data transfer is completed or the client disconnects.
+ #self.close()
+
+ def handle_timeout(self):
+ if self.cmd_channel.connected:
+ msg = "Active data channel timed out."
+ self.cmd_channel.respond("421 " + msg)
+ self.cmd_channel.log_cmd(self._cmd, self._normalized_addr, 421, msg)
+ self.close()
+
+ def handle_expt(self):
+ if self.cmd_channel.connected:
+ msg = "Can't connect to specified address."
+ self.cmd_channel.respond("425 " + msg)
+ self.cmd_channel.log_cmd(self._cmd, self._normalized_addr, 425, msg)
+ self.close()
+
+ def handle_error(self):
+ """Called to handle any uncaught exceptions."""
+ try:
+ raise
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except (socket.gaierror, socket.error):
+ pass
+ except:
+ self.log_exception(self)
+ self.handle_expt()
+
+ def close(self):
+ if not self._closed:
+ self._closed = True
+ asyncore.dispatcher.close(self)
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.cancel()
+
+
+class DTPHandler(object, asynchat.async_chat):
+ """Class handling server-data-transfer-process (server-DTP, see
+ RFC-959) managing data-transfer operations involving sending
+ and receiving data.
+
+ Class attributes:
+
+ - (int) timeout: the timeout which roughly is the maximum time we
+ permit data transfers to stall for with no progress. If the
+ timeout triggers, the remote client will be kicked off
+ (defaults 300).
+
+ - (int) ac_in_buffer_size: incoming data buffer size (defaults 65536)
+
+ - (int) ac_out_buffer_size: outgoing data buffer size (defaults 65536)
+ """
+
+ timeout = 300
+ ac_in_buffer_size = 65536
+ ac_out_buffer_size = 65536
+
+ def __init__(self, sock_obj, cmd_channel):
+ """Initialize the command channel.
+
+ - (instance) sock_obj: the socket object instance of the newly
+ established connection.
+ - (instance) cmd_channel: the command channel class instance.
+ """
+ self.cmd_channel = cmd_channel
+ self.file_obj = None
+ self.receive = False
+ self.transfer_finished = False
+ self.tot_bytes_sent = 0
+ self.tot_bytes_received = 0
+ self.cmd = None
+ self.log = cmd_channel.log
+ self.log_exception = cmd_channel.log_exception
+ self._data_wrapper = lambda x: x
+ self._lastdata = 0
+ self._closed = False
+ self._had_cr = False
+ self._start_time = time.time()
+ self._resp = None
+ self._offset = None
+ self._filefd = None
+ if self.timeout:
+ self._idler = CallEvery(self.timeout, self.handle_timeout,
+ _errback=self.handle_error)
+ else:
+ self._idler = None
+ try:
+ asynchat.async_chat.__init__(self, sock_obj)
+ except socket.error, err:
+ # if we get an exception here we want the dispatcher
+ # instance to set socket attribute before closing, see:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=188
+ asynchat.async_chat.__init__(self, socket.socket())
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=143
+ self.close()
+ if err.args[0] == errno.EINVAL:
+ return
+ self.handle_error()
+ return
+ # remove this instance from asyncore socket map
+ if not self.connected:
+ self.close()
+
+ def _use_sendfile(self, producer):
+ return self.cmd_channel.use_sendfile \
+ and isinstance(producer, FileProducer) \
+ and producer.type == 'i'
+
+ def push_with_producer(self, producer):
+ if self._use_sendfile(producer):
+ self._offset = producer.file.tell()
+ self._filefd = self.file_obj.fileno()
+ self.initiate_sendfile()
+ self.initiate_send = self.initiate_sendfile
+ else:
+ asynchat.async_chat.push_with_producer(self, producer)
+
+ def initiate_sendfile(self):
+ """A wrapper around sendfile."""
+ try:
+ sent = sendfile(self._fileno, self._filefd, self._offset,
+ self.ac_out_buffer_size)
+ except OSError, err:
+ if err.errno in (errno.EAGAIN, errno.EWOULDBLOCK, errno.EBUSY):
+ return
+ elif err.errno in _DISCONNECTED:
+ self.handle_close()
+ else:
+ raise
+ else:
+ if sent == 0:
+ # this signals the channel that the transfer is completed
+ self.discard_buffers()
+ self.handle_close()
+ else:
+ self._offset += sent
+ self.tot_bytes_sent += sent
+
+ # --- utility methods
+
+ def _posix_ascii_data_wrapper(self, chunk):
+ """The data wrapper used for receiving data in ASCII mode on
+ systems using a single line terminator, handling those cases
+ where CRLF ('\r\n') gets delivered in two chunks.
+ """
+ if self._had_cr:
+ chunk = '\r' + chunk
+
+ if chunk.endswith('\r'):
+ self._had_cr = True
+ chunk = chunk[:-1]
+ else:
+ self._had_cr = False
+
+ return chunk.replace('\r\n', os.linesep)
+
+ def enable_receiving(self, type, cmd):
+ """Enable receiving of data over the channel. Depending on the
+ TYPE currently in use it creates an appropriate wrapper for the
+ incoming data.
+
+ - (str) type: current transfer type, 'a' (ASCII) or 'i' (binary).
+ """
+ self.cmd = cmd
+ if type == 'a':
+ if os.linesep == '\r\n':
+ self._data_wrapper = lambda x: x
+ else:
+ self._data_wrapper = self._posix_ascii_data_wrapper
+ elif type == 'i':
+ self._data_wrapper = lambda x: x
+ else:
+ raise TypeError("unsupported type")
+ self.receive = True
+
+ def get_transmitted_bytes(self):
+ "Return the number of transmitted bytes."
+ return self.tot_bytes_sent + self.tot_bytes_received
+
+ def get_elapsed_time(self):
+ "Return the transfer elapsed time in seconds."
+ return time.time() - self._start_time
+
+ def transfer_in_progress(self):
+ "Return True if a transfer is in progress, else False."
+ return self.get_transmitted_bytes() != 0
+
+ # --- connection
+
+ def send(self, data):
+ result = asyncore.dispatcher.send(self, data)
+ self.tot_bytes_sent += result
+ return result
+
+ def refill_buffer (self):
+ """Overridden as a fix around http://bugs.python.org/issue1740572
+ (when the producer is consumed, close() was called instead of
+ handle_close()).
+ """
+ while 1:
+ if len(self.producer_fifo):
+ p = self.producer_fifo.first()
+ # a 'None' in the producer fifo is a sentinel,
+ # telling us to close the channel.
+ if p is None:
+ if not self.ac_out_buffer:
+ self.producer_fifo.pop()
+ #self.close()
+ self.handle_close()
+ return
+ elif isinstance(p, str):
+ self.producer_fifo.pop()
+ self.ac_out_buffer += p
+ return
+ data = p.more()
+ if data:
+ self.ac_out_buffer = self.ac_out_buffer + data
+ return
+ else:
+ self.producer_fifo.pop()
+ else:
+ return
+
+ def handle_read(self):
+ """Called when there is data waiting to be read."""
+ try:
+ chunk = self.recv(self.ac_in_buffer_size)
+ except socket.error:
+ self.handle_error()
+ else:
+ self.tot_bytes_received += len(chunk)
+ if not chunk:
+ self.transfer_finished = True
+ #self.close() # <-- asyncore.recv() already do that...
+ return
+ try:
+ self.file_obj.write(self._data_wrapper(chunk))
+ except OSError, err:
+ raise _FileReadWriteError(err)
+
+ def readable(self):
+ """Predicate for inclusion in the readable for select()."""
+ # we don't use asynchat's find terminator feature so we can
+ # freely avoid to call the original implementation
+ return self.receive
+
+ def writable(self):
+ """Predicate for inclusion in the writable for select()."""
+ return not self.receive and asynchat.async_chat.writable(self)
+
+ def handle_timeout(self):
+ """Called cyclically to check if data trasfer is stalling with
+ no progress in which case the client is kicked off.
+ """
+ if self.get_transmitted_bytes() > self._lastdata:
+ self._lastdata = self.get_transmitted_bytes()
+ else:
+ msg = "Data connection timed out."
+ self.log(msg)
+ self._resp = "421 " + msg
+ self.close()
+ self.cmd_channel.close_when_done()
+
+ def handle_expt(self):
+ """Called on "exceptional" data events."""
+ self.cmd_channel.respond("426 Connection error; transfer aborted.")
+ self.close()
+
+ def handle_error(self):
+ """Called when an exception is raised and not otherwise handled."""
+ try:
+ raise
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except socket.error, err:
+ # fixes around various bugs:
+ # - http://bugs.python.org/issue1736101
+ # - http://code.google.com/p/pyftpdlib/issues/detail?id=104
+ # - http://code.google.com/p/pyftpdlib/issues/detail?id=109
+ if err.args[0] in _DISCONNECTED:
+ self.handle_close()
+ return
+ else:
+ self.log_exception(self)
+ error = str(err.args[1])
+ # an error could occur in case we fail reading / writing
+ # from / to file (e.g. file system gets full)
+ except _FileReadWriteError, err:
+ error = _strerror(err.args[0])
+ except:
+ # some other exception occurred; we don't want to provide
+ # confidential error messages
+ self.log_exception(self)
+ error = "Internal error"
+ self._resp = "426 %s; transfer aborted." % error
+ self.close()
+
+ def handle_close(self):
+ """Called when the socket is closed."""
+ # If we used channel for receiving we assume that transfer is
+ # finished when client closes the connection, if we used channel
+ # for sending we have to check that all data has been sent
+ # (responding with 226) or not (responding with 426).
+ # In both cases handle_close() is automatically called by the
+ # underlying asynchat module.
+ if self.receive:
+ self.transfer_finished = True
+ else:
+ self.transfer_finished = len(self.producer_fifo) == 0
+ if self.transfer_finished:
+ self._resp = "226 Transfer complete."
+ else:
+ tot_bytes = self.get_transmitted_bytes()
+ self._resp = "426 Transfer aborted; %d bytes transmitted." % tot_bytes
+ self.close()
+
+ def close(self):
+ """Close the data channel, first attempting to close any remaining
+ file handles."""
+ if not self._closed:
+ self._closed = True
+ # RFC-959 says we must close the connection before replying
+ asyncore.dispatcher.close(self)
+ if self._resp:
+ self.cmd_channel.respond(self._resp)
+
+ if self.file_obj is not None and not self.file_obj.closed:
+ self.file_obj.close()
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.cancel()
+ if self.file_obj is not None:
+ filename = self.file_obj.name
+ elapsed_time = round(self.get_elapsed_time(), 3)
+ self.cmd_channel.log_transfer(cmd=self.cmd,
+ filename=self.file_obj.name,
+ receive=self.receive,
+ completed=self.transfer_finished,
+ elapsed=elapsed_time,
+ bytes=self.get_transmitted_bytes())
+ if self.transfer_finished:
+ if self.receive:
+ self.cmd_channel.on_file_received(filename)
+ else:
+ self.cmd_channel.on_file_sent(filename)
+ else:
+ if self.receive:
+ self.cmd_channel.on_incomplete_file_received(filename)
+ else:
+ self.cmd_channel.on_incomplete_file_sent(filename)
+ self.cmd_channel._on_dtp_close()
+
+
+class ThrottledDTPHandler(DTPHandler):
+ """A DTPHandler subclass which wraps sending and receiving in a data
+ counter and temporarily "sleeps" the channel so that you burst to no
+ more than x Kb/sec average.
+
+ - (int) read_limit: the maximum number of bytes to read (receive)
+ in one second (defaults to 0 == no limit).
+
+ - (int) write_limit: the maximum number of bytes to write (send)
+ in one second (defaults to 0 == no limit).
+
+ - (bool) auto_sized_buffers: this option only applies when read
+ and/or write limits are specified. When enabled it bumps down
+ the data buffer sizes so that they are never greater than read
+ and write limits which results in a less bursty and smoother
+ throughput (default: True).
+ """
+ read_limit = 0
+ write_limit = 0
+ auto_sized_buffers = True
+
+ def __init__(self, sock_obj, cmd_channel):
+ super(ThrottledDTPHandler, self).__init__(sock_obj, cmd_channel)
+ self._timenext = 0
+ self._datacount = 0
+ self.sleeping = False
+ self._throttler = None
+
+ if self.auto_sized_buffers:
+ if self.read_limit:
+ while self.ac_in_buffer_size > self.read_limit:
+ self.ac_in_buffer_size /= 2
+ if self.write_limit:
+ while self.ac_out_buffer_size > self.write_limit:
+ self.ac_out_buffer_size /= 2
+
+ def _use_sendfile(self, producer):
+ return False
+
+ def readable(self):
+ return not self.sleeping and super(ThrottledDTPHandler, self).readable()
+
+ def writable(self):
+ return not self.sleeping and super(ThrottledDTPHandler, self).writable()
+
+ def recv(self, buffer_size):
+ chunk = super(ThrottledDTPHandler, self).recv(buffer_size)
+ if self.read_limit:
+ self._throttle_bandwidth(len(chunk), self.read_limit)
+ return chunk
+
+ def send(self, data):
+ num_sent = super(ThrottledDTPHandler, self).send(data)
+ if self.write_limit:
+ self._throttle_bandwidth(num_sent, self.write_limit)
+ return num_sent
+
+ def _throttle_bandwidth(self, len_chunk, max_speed):
+ """A method which counts data transmitted so that you burst to
+ no more than x Kb/sec average.
+ """
+ self._datacount += len_chunk
+ if self._datacount >= max_speed:
+ self._datacount = 0
+ now = time.time()
+ sleepfor = self._timenext - now
+ if sleepfor > 0:
+ # we've passed bandwidth limits
+ def unsleep():
+ self.sleeping = False
+ self.sleeping = True
+ self._throttler = CallLater(sleepfor * 2, unsleep,
+ _errback=self.handle_error)
+ self._timenext = now + 1
+
+ def close(self):
+ if self._throttler is not None and not self._throttler.cancelled:
+ self._throttler.cancel()
+ super(ThrottledDTPHandler, self).close()
+
+
+# --- producers
+
+
+class FileProducer(object):
+ """Producer wrapper for file[-like] objects."""
+
+ buffer_size = 65536
+
+ def __init__(self, file, type):
+ """Initialize the producer with a data_wrapper appropriate to TYPE.
+
+ - (file) file: the file[-like] object.
+ - (str) type: the current TYPE, 'a' (ASCII) or 'i' (binary).
+ """
+ self.done = False
+ self.file = file
+ self.type = type
+ if type == 'a':
+ if os.linesep == '\r\n':
+ self._data_wrapper = lambda x: x
+ else:
+ self._data_wrapper = lambda x: x.replace(os.linesep, '\r\n')
+ elif type == 'i':
+ self._data_wrapper = lambda x: x
+ else:
+ raise TypeError("unsupported type")
+
+ def more(self):
+ """Attempt a chunk of data of size self.buffer_size."""
+ if self.done:
+ return ''
+ try:
+ data = self._data_wrapper(self.file.read(self.buffer_size))
+ except OSError, err:
+ raise _FileReadWriteError(err)
+ if not data:
+ self.done = True
+ if not self.file.closed:
+ self.file.close()
+ return data
+
+
+class BufferedIteratorProducer(object):
+ """Producer for iterator objects with buffer capabilities."""
+ # how many times iterator.next() will be called before
+ # returning some data
+ loops = 20
+
+ def __init__(self, iterator):
+ self.iterator = iterator
+
+ def more(self):
+ """Attempt a chunk of data from iterator by calling
+ its next() method different times.
+ """
+ buffer = []
+ for x in xrange(self.loops):
+ try:
+ buffer.append(self.iterator.next())
+ except StopIteration:
+ break
+ return ''.join(buffer)
+
+
+# --- filesystem
+
+class AbstractedFS(object):
+ """A class used to interact with the file system, providing a
+ cross-platform interface compatible with both Windows and
+ UNIX style filesystems where all paths use "/" separator.
+
+ AbstractedFS distinguishes between "real" filesystem paths and
+ "virtual" ftp paths emulating a UNIX chroot jail where the user
+ can not escape its home directory (example: real "/home/user"
+ path will be seen as "/" by the client)
+
+ It also provides some utility methods and wraps around all os.*
+ calls involving operations against the filesystem like creating
+ files or removing directories.
+ """
+
+ def __init__(self, root, cmd_channel):
+ """
+ - (str) root: the user "real" home directory (e.g. '/home/user')
+ - (instance) cmd_channel: the FTPHandler class instance
+ """
+ # Set initial current working directory.
+ # By default initial cwd is set to "/" to emulate a chroot jail.
+ # If a different behavior is desired (e.g. initial cwd = root,
+ # to reflect the real filesystem) users overriding this class
+ # are responsible to set _cwd attribute as necessary.
+ self._cwd = '/'
+ self._root = root
+ self.cmd_channel = cmd_channel
+
+ @property
+ def root(self):
+ """The user home directory."""
+ return self._root
+
+ @property
+ def cwd(self):
+ """The user current working directory."""
+ return self._cwd
+
+ @root.setter
+ def root(self, path):
+ self._root = path
+
+ @cwd.setter
+ def cwd(self, path):
+ self._cwd = path
+
+ # --- Pathname / conversion utilities
+
+ def ftpnorm(self, ftppath):
+ """Normalize a "virtual" ftp pathname (tipically the raw string
+ coming from client) depending on the current working directory.
+
+ Example (having "/foo" as current working directory):
+ >>> ftpnorm('bar')
+ '/foo/bar'
+
+ Note: directory separators are system independent ("/").
+ Pathname returned is always absolutized.
+ """
+ if os.path.isabs(ftppath):
+ p = os.path.normpath(ftppath)
+ else:
+ p = os.path.normpath(os.path.join(self.cwd, ftppath))
+ # normalize string in a standard web-path notation having '/'
+ # as separator.
+ p = p.replace("\\", "/")
+ # os.path.normpath supports UNC paths (e.g. "//a/b/c") but we
+ # don't need them. In case we get an UNC path we collapse
+ # redundant separators appearing at the beginning of the string
+ while p[:2] == '//':
+ p = p[1:]
+ # Anti path traversal: don't trust user input, in the event
+ # that self.cwd is not absolute, return "/" as a safety measure.
+ # This is for extra protection, maybe not really necessary.
+ if not os.path.isabs(p):
+ p = "/"
+ return p
+
+ def ftp2fs(self, ftppath):
+ """Translate a "virtual" ftp pathname (tipically the raw string
+ coming from client) into equivalent absolute "real" filesystem
+ pathname.
+
+ Example (having "/home/user" as root directory):
+ >>> ftp2fs("foo")
+ '/home/user/foo'
+
+ Note: directory separators are system dependent.
+ """
+ # as far as I know, it should always be path traversal safe...
+ if os.path.normpath(self.root) == os.sep:
+ return os.path.normpath(self.ftpnorm(ftppath))
+ else:
+ p = self.ftpnorm(ftppath)[1:]
+ return os.path.normpath(os.path.join(self.root, p))
+
+ def fs2ftp(self, fspath):
+ """Translate a "real" filesystem pathname into equivalent
+ absolute "virtual" ftp pathname depending on the user's
+ root directory.
+
+ Example (having "/home/user" as root directory):
+ >>> fs2ftp("/home/user/foo")
+ '/foo'
+
+ As for ftpnorm, directory separators are system independent
+ ("/") and pathname returned is always absolutized.
+
+ On invalid pathnames escaping from user's root directory
+ (e.g. "/home" when root is "/home/user") always return "/".
+ """
+ if os.path.isabs(fspath):
+ p = os.path.normpath(fspath)
+ else:
+ p = os.path.normpath(os.path.join(self.root, fspath))
+ if not self.validpath(p):
+ return '/'
+ p = p.replace(os.sep, "/")
+ p = p[len(self.root):]
+ if not p.startswith('/'):
+ p = '/' + p
+ return p
+
+ def validpath(self, path):
+ """Check whether the path belongs to user's home directory.
+ Expected argument is a "real" filesystem pathname.
+
+ If path is a symbolic link it is resolved to check its real
+ destination.
+
+ Pathnames escaping from user's root directory are considered
+ not valid.
+ """
+ root = self.realpath(self.root)
+ path = self.realpath(path)
+ if not root.endswith(os.sep):
+ root = root + os.sep
+ if not path.endswith(os.sep):
+ path = path + os.sep
+ if path[0:len(root)] == root:
+ return True
+ return False
+
+ # --- Wrapper methods around open() and tempfile.mkstemp
+
+ def open(self, filename, mode):
+ """Open a file returning its handler."""
+ return open(filename, mode)
+
+ def mkstemp(self, suffix='', prefix='', dir=None, mode='wb'):
+ """A wrap around tempfile.mkstemp creating a file with a unique
+ name. Unlike mkstemp it returns an object with a file-like
+ interface.
+ """
+ class FileWrapper:
+ def __init__(self, fd, name):
+ self.file = fd
+ self.name = name
+ def __getattr__(self, attr):
+ return getattr(self.file, attr)
+
+ text = not 'b' in mode
+ # max number of tries to find out a unique file name
+ tempfile.TMP_MAX = 50
+ fd, name = tempfile.mkstemp(suffix, prefix, dir, text=text)
+ file = os.fdopen(fd, mode)
+ return FileWrapper(file, name)
+
+ # --- Wrapper methods around os.* calls
+
+ def chdir(self, path):
+ """Change the current directory."""
+ # temporarily join the specified directory to see if we have
+ # permissions to do so
+ basedir = os.getcwd()
+ try:
+ os.chdir(path)
+ except OSError:
+ raise
+ else:
+ os.chdir(basedir)
+ self._cwd = self.fs2ftp(path)
+
+ def mkdir(self, path):
+ """Create the specified directory."""
+ os.mkdir(path)
+
+ def listdir(self, path):
+ """List the content of a directory."""
+ return os.listdir(path)
+
+ def rmdir(self, path):
+ """Remove the specified directory."""
+ os.rmdir(path)
+
+ def remove(self, path):
+ """Remove the specified file."""
+ os.remove(path)
+
+ def rename(self, src, dst):
+ """Rename the specified src file to the dst filename."""
+ os.rename(src, dst)
+
+ def chmod(self, path, mode):
+ """Change file/directory mode."""
+ if not hasattr(os, 'chmod'):
+ raise NotImplementedError
+ os.chmod(path, mode)
+
+ def stat(self, path):
+ """Perform a stat() system call on the given path."""
+ return os.stat(path)
+
+ def lstat(self, path):
+ """Like stat but does not follow symbolic links."""
+ return os.lstat(path)
+
+ if not hasattr(os, 'lstat'):
+ lstat = stat
+
+ # --- Wrapper methods around os.path.* calls
+
+ def isfile(self, path):
+ """Return True if path is a file."""
+ return os.path.isfile(path)
+
+ def islink(self, path):
+ """Return True if path is a symbolic link."""
+ return os.path.islink(path)
+
+ def isdir(self, path):
+ """Return True if path is a directory."""
+ return os.path.isdir(path)
+
+ def getsize(self, path):
+ """Return the size of the specified file in bytes."""
+ return os.path.getsize(path)
+
+ def getmtime(self, path):
+ """Return the last modified time as a number of seconds since
+ the epoch."""
+ return os.path.getmtime(path)
+
+ def realpath(self, path):
+ """Return the canonical version of path eliminating any
+ symbolic links encountered in the path (if they are
+ supported by the operating system).
+ """
+ return os.path.realpath(path)
+
+ def lexists(self, path):
+ """Return True if path refers to an existing path, including
+ a broken or circular symbolic link.
+ """
+ return os.path.lexists(path)
+
+ def get_user_by_uid(self, uid):
+ """Return the username associated with user id.
+ If this can't be determined return raw uid instead.
+ On Windows just return "owner".
+ """
+ if pwd is not None:
+ try:
+ return pwd.getpwuid(uid).pw_name
+ except KeyError:
+ return uid
+ else:
+ return "owner"
+
+ def get_group_by_gid(self, gid):
+ """Return the groupname associated with group id.
+ If this can't be determined return raw gid instead.
+ On Windows just return "group".
+ """
+ if grp is not None:
+ try:
+ return grp.getgrgid(gid).gr_name
+ except KeyError:
+ return gid
+ else:
+ return "group"
+
+ if hasattr(os, 'readlink'):
+ def readlink(self, path):
+ """Return a string representing the path to which a
+ symbolic link points.
+ """
+ return os.readlink(path)
+
+ # --- Listing utilities
+
+ def get_list_dir(self, path):
+ """"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])
+
+ def format_list(self, basedir, listing, ignore_err=True):
+ """Return an iterator object that yields the entries of given
+ directory emulating the "/bin/ls -lA" UNIX command output.
+
+ - (str) basedir: the absolute dirname.
+ - (list) listing: the names of the entries in basedir
+ - (bool) ignore_err: when False raise exception if os.lstat()
+ call fails.
+
+ On platforms which do not support the pwd and grp modules (such
+ as Windows), ownership is printed as "owner" and "group" as a
+ default, and number of hard links is always "1". On UNIX
+ systems, the actual owner, group, and number of links are
+ printed.
+
+ This is how output appears to client:
+
+ -rw-rw-rw- 1 owner group 7045120 Sep 02 3:47 music.mp3
+ drwxrwxrwx 1 owner group 0 Aug 31 18:50 e-books
+ -rw-rw-rw- 1 owner group 380 Sep 02 3:40 module.py
+ """
+ if self.cmd_channel.use_gmt_times:
+ timefunc = time.gmtime
+ else:
+ timefunc = time.localtime
+ now = time.time()
+ for basename in listing:
+ file = os.path.join(basedir, basename)
+ try:
+ st = self.lstat(file)
+ except OSError:
+ if ignore_err:
+ continue
+ raise
+ perms = _filemode(st.st_mode) # permissions
+ nlinks = st.st_nlink # number of links to inode
+ if not nlinks: # non-posix system, let's use a bogus value
+ nlinks = 1
+ size = st.st_size # file size
+ uname = self.get_user_by_uid(st.st_uid)
+ gname = self.get_group_by_gid(st.st_gid)
+ mtime = timefunc(st.st_mtime)
+ # if modificaton time > 6 months shows "month year"
+ # else "month hh:mm"; this matches proftpd format, see:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=187
+ if (now - st.st_mtime) > 180 * 24 * 60 * 60:
+ fmtstr = "%d %Y"
+ else:
+ fmtstr = "%d %H:%M"
+ try:
+ mtimestr = "%s %s" % (_months_map[mtime.tm_mon],
+ time.strftime(fmtstr, mtime))
+ except ValueError:
+ # It could be raised if last mtime happens to be too
+ # old (prior to year 1900) in which case we return
+ # the current time as last mtime.
+ mtime = timefunc()
+ mtimestr = "%s %s" % (_months_map[mtime.tm_mon],
+ time.strftime("%d %H:%M", mtime))
+
+ # if the file is a symlink, resolve it, e.g. "symlink -> realfile"
+ if stat.S_ISLNK(st.st_mode) and hasattr(self, 'readlink'):
+ basename = basename + " -> " + self.readlink(file)
+
+ # formatting is matched with proftpd ls output
+ yield "%s %3s %-8s %-8s %8s %s %s\r\n" % (perms, nlinks, uname, gname,
+ size, mtimestr, basename)
+
+ def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True):
+ """Return an iterator object that yields the entries of a given
+ directory or of a single file in a form suitable with MLSD and
+ MLST commands.
+
+ Every entry includes a list of "facts" referring the listed
+ element. See RFC-3659, chapter 7, to see what every single
+ fact stands for.
+
+ - (str) basedir: the absolute dirname.
+ - (list) listing: the names of the entries in basedir
+ - (str) perms: the string referencing the user permissions.
+ - (str) facts: the list of "facts" to be returned.
+ - (bool) ignore_err: when False raise exception if os.stat()
+ call fails.
+
+ Note that "facts" returned may change depending on the platform
+ and on what user specified by using the OPTS command.
+
+ This is how output could appear to the client issuing
+ a MLSD request:
+
+ type=file;size=156;perm=r;modify=20071029155301;unique=801cd2; music.mp3
+ type=dir;size=0;perm=el;modify=20071127230206;unique=801e33; ebooks
+ type=file;size=211;perm=r;modify=20071103093626;unique=801e32; module.py
+ """
+ if self.cmd_channel.use_gmt_times:
+ timefunc = time.gmtime
+ else:
+ timefunc = time.localtime
+ permdir = ''.join([x for x in perms if x not in 'arw'])
+ permfile = ''.join([x for x in perms if x not in 'celmp'])
+ if ('w' in perms) or ('a' in perms) or ('f' in perms):
+ permdir += 'c'
+ if 'd' in perms:
+ permdir += 'p'
+ for basename in listing:
+ file = os.path.join(basedir, basename)
+ retfacts = dict()
+ # in order to properly implement 'unique' fact (RFC-3659,
+ # chapter 7.5.2) we are supposed to follow symlinks, hence
+ # use os.stat() instead of os.lstat()
+ try:
+ st = self.stat(file)
+ except OSError:
+ if ignore_err:
+ continue
+ raise
+ # type + perm
+ if stat.S_ISDIR(st.st_mode):
+ if 'type' in facts:
+ if basename == '.':
+ retfacts['type'] = 'cdir'
+ elif basename == '..':
+ retfacts['type'] = 'pdir'
+ else:
+ retfacts['type'] = 'dir'
+ if 'perm' in facts:
+ retfacts['perm'] = permdir
+ else:
+ if 'type' in facts:
+ retfacts['type'] = 'file'
+ if 'perm' in facts:
+ retfacts['perm'] = permfile
+ if 'size' in facts:
+ retfacts['size'] = st.st_size # file size
+ # last modification time
+ if 'modify' in facts:
+ try:
+ retfacts['modify'] = time.strftime("%Y%m%d%H%M%S",
+ timefunc(st.st_mtime))
+ # it could be raised if last mtime happens to be too old
+ # (prior to year 1900)
+ except ValueError:
+ pass
+ if 'create' in facts:
+ # on Windows we can provide also the creation time
+ try:
+ retfacts['create'] = time.strftime("%Y%m%d%H%M%S",
+ timefunc(st.st_ctime))
+ except ValueError:
+ pass
+ # UNIX only
+ if 'unix.mode' in facts:
+ retfacts['unix.mode'] = oct(st.st_mode & 0777)
+ if 'unix.uid' in facts:
+ retfacts['unix.uid'] = st.st_uid
+ if 'unix.gid' in facts:
+ retfacts['unix.gid'] = 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
+ # uniqueness for the file listed.
+ # The same approach is used by pure-ftpd.
+ # Implementors who want to provide unique fact on other
+ # platforms should use some platform-specific method (e.g.
+ # on Windows NTFS filesystems MTF records could be used).
+ if 'unique' in facts:
+ retfacts['unique'] = "%xg%x" % (st.st_dev, st.st_ino)
+
+ # facts can be in any order but we sort them by name
+ factstring = "".join(["%s=%s;" % (x, retfacts[x]) \
+ for x in sorted(retfacts.keys())])
+ yield "%s %s\r\n" % (factstring, basename)
+
+
+# --- FTP
+
+class FTPHandler(object, asynchat.async_chat):
+ """Implements the FTP server Protocol Interpreter (see RFC-959),
+ handling commands received from the client on the control channel.
+
+ All relevant session information is stored in class attributes
+ reproduced below and can be modified before instantiating this
+ class.
+
+ - (int) timeout:
+ The timeout which is the maximum time a remote client may spend
+ between FTP commands. If the timeout triggers, the remote client
+ will be kicked off. Defaults to 300 seconds.
+
+ - (str) banner: the string sent when client connects.
+
+ - (int) max_login_attempts:
+ the maximum number of wrong authentications before disconnecting
+ the client (default 3).
+
+ - (bool)permit_foreign_addresses:
+ FTP site-to-site transfer feature: also referenced as "FXP" it
+ permits for transferring a file between two remote FTP servers
+ without the transfer going through the client's host (not
+ recommended for security reasons as described in RFC-2577).
+ Having this attribute set to False means that all data
+ connections from/to remote IP addresses which do not match the
+ client's IP address will be dropped (defualt False).
+
+ - (bool) permit_privileged_ports:
+ set to True if you want to permit active data connections (PORT)
+ over privileged ports (not recommended, defaulting to False).
+
+ - (str) masquerade_address:
+ the "masqueraded" IP address to provide along PASV reply when
+ pyftpdlib is running behind a NAT or other types of gateways.
+ When configured pyftpdlib will hide its local address and
+ instead use the public address of your NAT (default None).
+
+ - (dict) masquerade_address_map:
+ in case the server has multiple IP addresses which are all
+ behind a NAT router, you may wish to specify individual
+ masquerade_addresses for each of them. The map expects a
+ dictionary containing private IP addresses as keys, and their
+ corresponding public (masquerade) addresses as values.
+
+ - (list) passive_ports:
+ what ports the ftpd will use for its passive data transfers.
+ Value expected is a list of integers (e.g. range(60000, 65535)).
+ When configured pyftpdlib will no longer use kernel-assigned
+ random ports (default None).
+
+ - (bool) use_gmt_times:
+ when True causes the server to report all ls and MDTM times in
+ GMT and not local time (default True).
+
+ - (bool) use_sendfile: when True uses sendfile() system call to
+ send a file resulting in faster uploads (from server to client).
+ Works on UNIX only and requires pysendfile module to be
+ installed separately:
+ http://code.google.com/p/pysendfile/
+ Automatically defaults to True if pysendfile module is
+ installed.
+
+ - (bool) tcp_no_delay: controls the use of the TCP_NODELAY socket
+ option which disables the Nagle algorithm resulting in
+ significantly better performances (default True on all systems
+ where it is supported).
+
+ All relevant instance attributes initialized when client connects
+ are reproduced below. You may be interested in them in case you
+ want to subclass the original FTPHandler.
+
+ - (bool) authenticated: True if client authenticated himself.
+ - (str) username: the name of the connected user (if any).
+ - (int) attempted_logins: number of currently attempted logins.
+ - (str) current_type: the current transfer type (default "a")
+ - (int) af: the connection's address family (IPv4/IPv6)
+ - (instance) server: the FTPServer class instance.
+ - (instance) data_channel: the data channel instance (if any).
+ """
+ # these are overridable defaults
+
+ # default classes
+ authorizer = DummyAuthorizer()
+ active_dtp = ActiveDTP
+ passive_dtp = PassiveDTP
+ dtp_handler = DTPHandler
+ abstracted_fs = AbstractedFS
+ proto_cmds = proto_cmds
+
+ # session attributes (explained in the docstring)
+ timeout = 300
+ banner = "pyftpdlib %s ready." % __ver__
+ max_login_attempts = 3
+ permit_foreign_addresses = False
+ permit_privileged_ports = False
+ masquerade_address = None
+ masquerade_address_map = {}
+ passive_ports = None
+ use_gmt_times = True
+ use_sendfile = sendfile is not None
+ tcp_no_delay = hasattr(socket, "TCP_NODELAY")
+
+ def __init__(self, conn, server):
+ """Initialize the command channel.
+
+ - (instance) conn: the socket object instance of the newly
+ established connection.
+ - (instance) server: the ftp server class instance.
+ """
+ # public session attributes
+ self.server = server
+ self.fs = None
+ self.authenticated = False
+ self.username = ""
+ self.password = ""
+ self.attempted_logins = 0
+ self.sleeping = False
+ self.data_channel = None
+ self.remote_ip = ""
+ self.remote_port = ""
+
+ # private session attributes
+ self._last_response = ""
+ self._current_type = 'a'
+ self._restart_position = 0
+ self._quit_pending = False
+ self._af = -1
+ self._in_buffer = []
+ self._in_buffer_len = 0
+ self._epsvall = False
+ self._dtp_acceptor = None
+ self._dtp_connector = None
+ self._in_dtp_queue = None
+ self._out_dtp_queue = None
+ self._closed = False
+ self._extra_feats = []
+ self._current_facts = ['type', 'perm', 'size', 'modify']
+ self._rnfr = None
+ if self.timeout:
+ self._idler = CallLater(self.timeout, self.handle_timeout,
+ _errback=self.handle_error)
+ else:
+ self._idler = None
+ if os.name == 'posix':
+ 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')
+
+ try:
+ asynchat.async_chat.__init__(self, conn)
+ except socket.error, err:
+ # if we get an exception here we want the dispatcher
+ # instance to set socket attribute before closing, see:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=188
+ asynchat.async_chat.__init__(self, socket.socket())
+ self.close()
+ if err.args[0] == errno.EINVAL:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=143
+ return
+ self.handle_error()
+ return
+ self.set_terminator("\r\n")
+
+ # connection properties
+ try:
+ self.remote_ip, self.remote_port = self.socket.getpeername()[:2]
+ except socket.error, err:
+ # A race condition may occur if the other end is closing
+ # before we can get the peername, hence ENOTCONN (see issue
+ # #100) while EINVAL can occur on OSX (see issue #143).
+ self.connected = False
+ if err.args[0] in (errno.ENOTCONN, errno.EINVAL):
+ self.close()
+ else:
+ self.handle_error()
+ return
+
+ if hasattr(self.socket, 'family'):
+ self._af = self.socket.family
+ else: # python < 2.5
+ ip, port = self.socket.getsockname()[:2]
+ self._af = socket.getaddrinfo(ip, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM)[0][0]
+
+ # try to handle urgent data inline
+ try:
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1)
+ except socket.error:
+ pass
+
+ # disable Nagle algorithm for the control socket only, resulting
+ # in significantly better performances
+ if self.tcp_no_delay:
+ try:
+ self.socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+ except socket.error:
+ pass
+
+ # remove this instance from asyncore socket_map
+ if not self.connected:
+ self.close()
+
+ def handle(self):
+ """Return a 220 'ready' response to the client over the command
+ channel.
+ """
+ if len(self.banner) <= 75:
+ self.respond("220 %s" % str(self.banner))
+ else:
+ 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 temporarily unavailable."
+ 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
+ # accepting new connections. Since this could cause asyncore to
+ # run out of fds (...and exposes the server to DoS attacks), we
+ # immediately close the channel by using close() instead of
+ # close_when_done(). If data has not been sent yet client will
+ # be silently disconnected.
+ self.close()
+
+ 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.log(msg)
+ self.close_when_done()
+
+ def handle_timeout(self):
+ """Called when client does not send any command within the time
+ specified in <timeout> attribute."""
+ msg = "Control connection timed out."
+ self.log(msg)
+ self.respond("421 " + msg)
+ self.close_when_done()
+
+ # --- asyncore / asynchat overridden methods
+
+ def readable(self):
+ # Checking for self.connected seems to be necessary as per:
+ # http://code.google.com/p/pyftpdlib/issues/detail?id=188#c18
+ # In contrast to DTPHandler, here we are not interested in
+ # attempting to receive any further data from a closed socket.
+ return not self.sleeping and self.connected \
+ and asynchat.async_chat.readable(self)
+
+ def writable(self):
+ return not self.sleeping and self.connected \
+ and asynchat.async_chat.writable(self)
+
+ def collect_incoming_data(self, data):
+ """Read incoming data and append to the input buffer."""
+ self._in_buffer.append(data)
+ self._in_buffer_len += len(data)
+ # Flush buffer if it gets too long (possible DoS attacks).
+ # RFC-959 specifies that a 500 response could be given in
+ # such cases
+ 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._in_buffer = []
+ self._in_buffer_len = 0
+
+ def found_terminator(self):
+ r"""Called when the incoming data stream matches the \r\n
+ terminator.
+ """
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.reset()
+
+ line = ''.join(self._in_buffer)
+ self._in_buffer = []
+ self._in_buffer_len = 0
+
+ cmd = line.split(' ')[0].upper()
+ arg = line[len(cmd)+1:]
+ kwargs = {}
+ if cmd == "SITE" and arg:
+ cmd = "SITE %s" % arg.split(' ')[0].upper()
+ arg = line[len(cmd)+1:]
+
+ if cmd != 'PASS':
+ self.logline("<== %s" % line)
+ else:
+ self.logline("<== %s %s" % (line.split(' ')[0], '*' * 6))
+
+ # Recognize those commands having a "special semantic". They
+ # should be sent by following the RFC-959 procedure of sending
+ # Telnet IP/Synch sequence (chr 242 and 255) as OOB data but
+ # since many ftp clients don't do it correctly we check the
+ # last 4 characters only.
+ if not cmd in self.proto_cmds:
+ if cmd[-4:] in ('ABOR', 'STAT', 'QUIT'):
+ cmd = cmd[-4:]
+ else:
+ msg = 'Command "%s" not understood.' % cmd
+ self.respond('500 ' + msg)
+ if cmd:
+ self.log_cmd(cmd, arg, 500, msg)
+ return
+
+ if not arg and self.proto_cmds[cmd]['arg'] == True:
+ msg = "Syntax error: command needs an argument."
+ self.respond("501 " + msg)
+ self.log_cmd(cmd, "", 501, msg)
+ return
+ if arg and self.proto_cmds[cmd]['arg'] == False:
+ msg = "Syntax error: command does not accept arguments."
+ self.respond("501 " + msg)
+ self.log_cmd(cmd, arg, 501, msg)
+ return
+
+ if not self.authenticated:
+ if self.proto_cmds[cmd]['auth'] or (cmd == 'STAT' and arg):
+ msg = "Log in with USER and PASS first."
+ self.respond("530 " + msg)
+ self.log_cmd(cmd, arg, 530, msg)
+ else:
+ # call the proper ftp_* method
+ self.process_command(cmd, arg)
+ return
+ else:
+ if (cmd == 'STAT') and not arg:
+ self.ftp_STAT('')
+ return
+
+ # for file-system related commands check whether real path
+ # destination is valid
+ if self.proto_cmds[cmd]['perm'] and (cmd != 'STOU'):
+ if cmd in ('CWD', 'XCWD'):
+ arg = self.fs.ftp2fs(arg or '/')
+ elif cmd in ('CDUP', 'XCUP'):
+ arg = self.fs.ftp2fs('..')
+ elif cmd == 'LIST':
+ if arg.lower() in ('-a', '-l', '-al', '-la'):
+ arg = self.fs.ftp2fs(self.fs.cwd)
+ else:
+ arg = self.fs.ftp2fs(arg or self.fs.cwd)
+ elif cmd == 'STAT':
+ if glob.has_magic(arg):
+ msg = 'Globbing not supported.'
+ self.respond('550 ' + msg)
+ self.log_cmd(cmd, arg, 550, msg)
+ return
+ arg = self.fs.ftp2fs(arg or self.fs.cwd)
+ elif cmd == 'SITE CHMOD':
+ if not ' ' in arg:
+ msg = "Syntax error: command needs two arguments."
+ self.respond("501 " + msg)
+ self.log_cmd(cmd, "", 501, msg)
+ return
+ else:
+ mode, arg = arg.split(' ', 1)
+ arg = self.fs.ftp2fs(arg)
+ kwargs = dict(mode=mode)
+ else: # LIST, NLST, MLSD, MLST
+ arg = self.fs.ftp2fs(arg or self.fs.cwd)
+
+ if not self.fs.validpath(arg):
+ line = self.fs.fs2ftp(arg)
+ msg = '"%s" points to a path which is outside ' \
+ "the user's root directory" % line
+ self.respond("550 %s." % msg)
+ self.log_cmd(cmd, arg, 550, msg)
+ return
+
+ # check permission
+ perm = self.proto_cmds[cmd]['perm']
+ if perm is not None and cmd != 'STOU':
+ if not self.authorizer.has_perm(self.username, perm, arg):
+ msg = "Not enough privileges."
+ self.respond("550 " + msg)
+ self.log_cmd(cmd, arg, 550, msg)
+ return
+
+ # call the proper ftp_* method
+ self.process_command(cmd, arg, **kwargs)
+
+ def process_command(self, cmd, *args, **kwargs):
+ """Process command by calling the corresponding ftp_* class
+ method (e.g. for received command "MKD pathname", ftp_MKD()
+ method is called with "pathname" as the argument).
+ """
+ self._last_response = ""
+ method = getattr(self, 'ftp_' + cmd.replace(' ', '_'))
+ method(*args, **kwargs)
+ if self._last_response:
+ code = int(self._last_response[:3])
+ resp = self._last_response[4:]
+ self.log_cmd(cmd, args[0], code, resp)
+
+ def handle_expt(self):
+ """Called when there is out of band (OOB) data to be read.
+ This might happen in case of such clients strictly following
+ the RFC-959 directives of sending Telnet IP and Synch as OOB
+ data before issuing ABOR, STAT and QUIT commands.
+ It should never be called since the SO_OOBINLINE option is
+ enabled except on some systems like FreeBSD where it doesn't
+ seem to have effect.
+ """
+ if hasattr(socket, 'MSG_OOB'):
+ try:
+ data = self.socket.recv(1024, socket.MSG_OOB)
+ except socket.error, err:
+ if err.args[0] == errno.EINVAL:
+ return
+ else:
+ self._in_buffer.append(data)
+ return
+ self.log("Can't handle OOB data.")
+ self.close()
+
+ def handle_error(self):
+ try:
+ raise
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except socket.error, err:
+ # fixes around various bugs:
+ # - http://bugs.python.org/issue1736101
+ # - http://code.google.com/p/pyftpdlib/issues/detail?id=104
+ # - http://code.google.com/p/pyftpdlib/issues/detail?id=109
+ if err.args[0] in _DISCONNECTED:
+ self.handle_close()
+ return
+ else:
+ self.log_exception(self)
+ except:
+ self.log_exception(self)
+ self.close()
+
+ def handle_close(self):
+ self.close()
+
+ def close(self):
+ """Close the current channel disconnecting the client."""
+ if not self._closed:
+ self._closed = True
+ asynchat.async_chat.close(self)
+ self.connected = False
+
+ self._shutdown_connecting_dtp()
+
+ if self.data_channel is not None:
+ self.data_channel.close()
+ del self.data_channel
+
+ del self._out_dtp_queue
+ del self._in_dtp_queue
+
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.cancel()
+
+ # remove client IP address from ip map
+ if self.remote_ip in self.server.ip_map:
+ self.server.ip_map.remove(self.remote_ip)
+
+ if self.fs is not None:
+ self.fs.cmd_channel = None
+ self.fs = None
+ self.log("Disconnected.")
+
+ def _shutdown_connecting_dtp(self):
+ """Close any ActiveDTP or PassiveDTP instance waiting to
+ establish a connection (passive or active).
+ """
+ if self._dtp_acceptor is not None:
+ self._dtp_acceptor.close()
+ self._dtp_acceptor = None
+ if self._dtp_connector is not None:
+ self._dtp_connector.close()
+ self._dtp_connector = None
+
+ # --- public callbacks
+ # Note: to run a time consuming task make sure to use a separate
+ # process or thread (see FAQs).
+
+ def on_file_sent(self, file):
+ """Called every time a file has been succesfully sent.
+ "file" is the absolute name of the file just being sent.
+ """
+
+ def on_file_received(self, file):
+ """Called every time a file has been succesfully received.
+ "file" is the absolute name of the file just being received.
+ """
+
+ def on_incomplete_file_sent(self, file):
+ """Called every time a file has not been entirely sent.
+ (e.g. ABOR during transfer or client disconnected).
+ "file" is the absolute name of that file.
+ """
+
+ def on_incomplete_file_received(self, file):
+ """Called every time a file has not been entirely received
+ (e.g. ABOR during transfer or client disconnected).
+ "file" is the absolute name of that file.
+ """
+
+ def on_login(self, username):
+ """Called on user login."""
+
+ def on_login_failed(self, username, password):
+ """Called on failed user login.
+ At this point client might have already been disconnected if it
+ failed too many times.
+ """
+
+ def on_logout(self, username):
+ """Called when user logs out due to QUIT or USER issued twice."""
+
+
+ # --- internal callbacks
+
+ def _on_dtp_connection(self):
+ """Called every time data channel connects, either active or
+ passive.
+
+ Incoming and outgoing queues are checked for pending data.
+ If outbound data is pending, it is pushed into the data channel.
+ If awaiting inbound data, the data channel is enabled for
+ receiving.
+ """
+ # Close accepting DTP only. By closing ActiveDTP DTPHandler
+ # would receive a closed socket object.
+ #self._shutdown_connecting_dtp()
+ if self._dtp_acceptor is not None:
+ self._dtp_acceptor.close()
+ self._dtp_acceptor = None
+
+ # stop the idle timer as long as the data transfer is not finished
+ if self._idler is not None and not self._idler.cancelled:
+ self._idler.cancel()
+
+ # check for data to send
+ if self._out_dtp_queue is not None:
+ data, isproducer, file, cmd = self._out_dtp_queue
+ self._out_dtp_queue = None
+ self.data_channel.cmd = cmd
+ if file:
+ self.data_channel.file_obj = file
+ try:
+ if not isproducer:
+ self.data_channel.push(data)
+ else:
+ self.data_channel.push_with_producer(data)
+ if self.data_channel is not None:
+ self.data_channel.close_when_done()
+ except:
+ # dealing with this exception is up to DTP (see bug #84)
+ self.data_channel.handle_error()
+
+ # check for data to receive
+ elif self._in_dtp_queue is not None:
+ file, cmd = self._in_dtp_queue
+ self.data_channel.file_obj = file
+ self._in_dtp_queue = None
+ self.data_channel.enable_receiving(self._current_type, cmd)
+
+ def _on_dtp_close(self):
+ """Called every time the data channel is closed."""
+ self.data_channel = None
+ if self._quit_pending:
+ self.close()
+ elif self.timeout:
+ # data transfer finished, restart the idle timer
+ self._idler = CallLater(self.timeout, self.handle_timeout,
+ _errback=self.handle_error)
+
+ # --- utility
+
+ def respond(self, resp):
+ """Send a response to the client using the command channel."""
+ self._last_response = resp
+ self.push(resp + '\r\n')
+ self.logline('==> %s' % resp)
+
+ def push_dtp_data(self, data, isproducer=False, file=None, cmd=None):
+ """Pushes data into the data channel.
+
+ It is usually called for those commands requiring some data to
+ be sent over the data channel (e.g. RETR).
+ If data channel does not exist yet, it queues the data to send
+ later; data will then be pushed into data channel when
+ _on_dtp_connection() will be called.
+
+ - (str/classobj) data: the data to send which may be a string
+ or a producer object).
+ - (bool) isproducer: whether treat data as a producer.
+ - (file) file: the file[-like] object to send (if any).
+ """
+ if self.data_channel is not None:
+ self.respond("125 Data connection already open. Transfer starting.")
+ if file:
+ self.data_channel.file_obj = file
+ try:
+ if not isproducer:
+ self.data_channel.push(data)
+ else:
+ self.data_channel.push_with_producer(data)
+ if self.data_channel is not None:
+ self.data_channel.cmd = cmd
+ self.data_channel.close_when_done()
+ except:
+ # dealing with this exception is up to DTP (see bug #84)
+ self.data_channel.handle_error()
+ else:
+ self.respond("150 File status okay. About to open data connection.")
+ self._out_dtp_queue = (data, isproducer, file, cmd)
+
+ def flush_account(self):
+ """Flush account information by clearing attributes that need
+ to be reset on a REIN or new USER command.
+ """
+ self._shutdown_connecting_dtp()
+ # if there's a transfer in progress RFC-959 states we are
+ # supposed to let it finish
+ if self.data_channel is not None:
+ if not self.data_channel.transfer_in_progress():
+ self.data_channel.close()
+ self.data_channel = None
+
+ username = self.username
+ self.authenticated = False
+ self.username = ""
+ self.password = ""
+ self.attempted_logins = 0
+ self._current_type = 'a'
+ self._restart_position = 0
+ self._quit_pending = False
+ self.sleeping = False
+ self._in_dtp_queue = None
+ self._rnfr = None
+ self._out_dtp_queue = None
+ if username:
+ self.on_logout(username)
+
+ def run_as_current_user(self, function, *args, **kwargs):
+ """Execute a function impersonating the current logged-in user."""
+ self.authorizer.impersonate_user(self.username, self.password)
+ try:
+ return function(*args, **kwargs)
+ finally:
+ self.authorizer.terminate_impersonation(self.username)
+
+ # --- logging wrappers
+
+ def log(self, msg):
+ """Log a message, including additional identifying session data."""
+ 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))
+
+ def logerror(self, msg):
+ """Log an error including additional indentifying session data."""
+ logerror("[%s]@%s:%s %s" % (self.username, self.remote_ip,
+ self.remote_port, msg))
+
+ def log_exception(self, instance):
+ """Log an unhandled exception. 'instance' is the instance
+ where the exception was generated.
+ """
+ self.logerror("unhandled exception in instance %r\n%s" \
+ % (instance, traceback.format_exc()))
+
+ def log_cmd(self, cmd, arg, respcode, respstr):
+ """Log commands and responses in a standardized format.
+
+ - (str) cmd:
+ the command sent by client
+
+ - (str) arg:
+ the command argument sent by client.
+ For filesystem commands such as DELE, MKD, etc. this is
+ already represented as an absolute real filesystem path
+ like "/home/user/file.ext".
+
+ - (int) respcode:
+ the response code as being sent by server. Response codes
+ starting with 4xx or 5xx are returned if the command has
+ been rejected for some reason.
+
+ - (str) respstr:
+ the response string as being sent by server.
+
+ By default only DELE, RMD, RNFR, RNTO, MKD commands are logged
+ and the output is redirected to self.log method (the main logger).
+
+ Can be overridden to provide alternate formats or to log
+ further commands.
+ """
+ if cmd in ("DELE", "RMD", "RNFR", "RNTO", "MKD"):
+ line = '"%s" %s' % (' '.join([cmd, str(arg)]).strip(), respcode)
+ self.log(line)
+
+ def log_transfer(self, cmd, filename, receive, completed, elapsed, bytes):
+ """Log all file transfers in a standardized format.
+
+ - (str) cmd:
+ the original command who caused the tranfer.
+
+ - (str) filename:
+ the absolutized name of the file on disk.
+
+ - (bool) receive:
+ True if the transfer was used for client uploading (STOR,
+ STOU, APPE), False otherwise (RETR).
+
+ - (bool) completed:
+ True if the file has been entirely sent, else False.
+
+ - (float) elapsed:
+ transfer elapsed time in seconds.
+
+ - (int) bytes:
+ number of bytes transmitted.
+ """
+ line = '"%s %s" completed=%s bytes=%s seconds=%s' % \
+ (cmd, filename, completed and 1 or 0, bytes, elapsed)
+ self.log(line)
+
+
+ # --- connection
+
+ def _make_eport(self, ip, port):
+ """Establish an active data channel with remote client which
+ issued a PORT or EPRT command.
+ """
+ # FTP bounce attacks protection: according to RFC-2577 it's
+ # recommended to reject PORT if IP address specified in it
+ # does not match client IP address.
+ remote_ip = self.remote_ip
+ if remote_ip.startswith('::ffff:'):
+ # In this scenario, the server has an IPv6 socket, but
+ # the remote client is using IPv4 and its address is
+ # represented as an IPv4-mapped IPv6 address which
+ # looks like this ::ffff:151.12.5.65, see:
+ # http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_addresses
+ # http://tools.ietf.org/html/rfc3493.html#section-3.7
+ # We truncate the first bytes to make it look like a
+ # common IPv4 address.
+ remote_ip = remote_ip[7:]
+ if not self.permit_foreign_addresses and ip != remote_ip:
+ self.log("Rejected data connection to foreign address %s:%s."
+ % (ip, port))
+ self.respond("501 Can't connect to a foreign address.")
+ return
+
+ # ...another RFC-2577 recommendation is rejecting connections
+ # to privileged ports (< 1024) for security reasons.
+ if not self.permit_privileged_ports and port < 1024:
+ self.log('PORT against the privileged port "%s" refused.' % port)
+ self.respond("501 Can't connect over a privileged port.")
+ return
+
+ # close establishing DTP instances, if any
+ self._shutdown_connecting_dtp()
+
+ if self.data_channel is not None:
+ self.data_channel.close()
+ self.data_channel = None
+
+ # make sure we are not hitting the max connections limit
+ if self.server.max_cons:
+ if len(asyncore.socket_map) >= self.server.max_cons:
+ msg = "Too many connections. Can't open data channel."
+ self.respond("425 %s" %msg)
+ self.log(msg)
+ return
+
+ # open data channel
+ self._dtp_connector = self.active_dtp(ip, port, self)
+
+ def _make_epasv(self, extmode=False):
+ """Initialize a passive data channel with remote client which
+ issued a PASV or EPSV command.
+ If extmode argument is True we assume that client issued EPSV in
+ which case extended passive mode will be used (see RFC-2428).
+ """
+ # close establishing DTP instances, if any
+ self._shutdown_connecting_dtp()
+
+ # close established data connections, if any
+ if self.data_channel is not None:
+ self.data_channel.close()
+ self.data_channel = None
+
+ # make sure we are not hitting the max connections limit
+ if self.server.max_cons:
+ if len(asyncore.socket_map) >= self.server.max_cons:
+ msg = "Too many connections. Can't open data channel."
+ self.respond("425 %s" %msg)
+ self.log(msg)
+ return
+
+ # open data channel
+ self._dtp_acceptor = self.passive_dtp(self, extmode)
+
+ def ftp_PORT(self, line):
+ """Start an active data channel by using IPv4."""
+ if self._epsvall:
+ self.respond("501 PORT not allowed after EPSV ALL.")
+ return
+ # Parse PORT request for getting IP and PORT.
+ # Request comes in as:
+ # > h1,h2,h3,h4,p1,p2
+ # ...where the client's IP address is h1.h2.h3.h4 and the TCP
+ # port number is (p1 * 256) + p2.
+ try:
+ addr = map(int, line.split(','))
+ if len(addr) != 6:
+ raise ValueError
+ for x in addr[:4]:
+ if not 0 <= x <= 255:
+ raise ValueError
+ ip = '%d.%d.%d.%d' % tuple(addr[:4])
+ port = (addr[4] * 256) + addr[5]
+ if not 0 <= port <= 65535:
+ raise ValueError
+ except (ValueError, OverflowError):
+ self.respond("501 Invalid PORT format.")
+ return
+ self._make_eport(ip, port)
+
+ def ftp_EPRT(self, line):
+ """Start an active data channel by choosing the network protocol
+ to use (IPv4/IPv6) as defined in RFC-2428.
+ """
+ if self._epsvall:
+ self.respond("501 EPRT not allowed after EPSV ALL.")
+ return
+ # Parse EPRT request for getting protocol, IP and PORT.
+ # Request comes in as:
+ # <d>proto<d>ip<d>port<d>
+ # ...where <d> is an arbitrary delimiter character (usually "|") and
+ # <proto> is the network protocol to use (1 for IPv4, 2 for IPv6).
+ try:
+ af, ip, port = line.split(line[0])[1:-1]
+ port = int(port)
+ if not 0 <= port <= 65535:
+ raise ValueError
+ except (ValueError, IndexError, OverflowError):
+ self.respond("501 Invalid EPRT format.")
+ return
+
+ if af == "1":
+ if self._af != socket.AF_INET:
+ self.respond('522 Network protocol not supported (use 2).')
+ else:
+ try:
+ octs = map(int, ip.split('.'))
+ if len(octs) != 4:
+ raise ValueError
+ for x in octs:
+ if not 0 <= x <= 255:
+ raise ValueError
+ except (ValueError, OverflowError):
+ self.respond("501 Invalid EPRT format.")
+ else:
+ self._make_eport(ip, port)
+ elif af == "2":
+ if self._af == socket.AF_INET:
+ self.respond('522 Network protocol not supported (use 1).')
+ else:
+ self._make_eport(ip, port)
+ else:
+ if self._af == socket.AF_INET:
+ self.respond('501 Unknown network protocol (use 1).')
+ else:
+ self.respond('501 Unknown network protocol (use 2).')
+
+ def ftp_PASV(self, line):
+ """Start a passive data channel by using IPv4."""
+ if self._epsvall:
+ self.respond("501 PASV not allowed after EPSV ALL.")
+ return
+ self._make_epasv(extmode=False)
+
+ def ftp_EPSV(self, line):
+ """Start a passive data channel by using IPv4 or IPv6 as defined
+ in RFC-2428.
+ """
+ # RFC-2428 specifies that if an optional parameter is given,
+ # we have to determine the address family from that otherwise
+ # use the same address family used on the control connection.
+ # In such a scenario a client may use IPv4 on the control channel
+ # and choose to use IPv6 for the data channel.
+ # But how could we use IPv6 on the data channel without knowing
+ # 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
+ # family used on the control connection.
+ if not line:
+ self._make_epasv(extmode=True)
+ # IPv4
+ elif line == "1":
+ if self._af != socket.AF_INET:
+ self.respond('522 Network protocol not supported (use 2).')
+ else:
+ self._make_epasv(extmode=True)
+ # IPv6
+ elif line == "2":
+ if self._af == socket.AF_INET:
+ self.respond('522 Network protocol not supported (use 1).')
+ else:
+ self._make_epasv(extmode=True)
+ elif line.lower() == 'all':
+ self._epsvall = True
+ self.respond('220 Other commands other than EPSV are now disabled.')
+ else:
+ if self._af == socket.AF_INET:
+ self.respond('501 Unknown network protocol (use 1).')
+ else:
+ self.respond('501 Unknown network protocol (use 2).')
+
+ def ftp_QUIT(self, line):
+ """Quit the current session disconnecting the client."""
+ if self.authenticated:
+ msg_quit = self.authorizer.get_msg_quit(self.username)
+ else:
+ msg_quit = "Goodbye."
+ if len(msg_quit) <= 75:
+ self.respond("221 %s" % msg_quit)
+ else:
+ self.push("221-%s\r\n" % msg_quit)
+ self.respond("221 ")
+
+ # From RFC-959:
+ # If file transfer is in progress, the connection must remain
+ # open for result response and the server will then close it.
+ # We also stop responding to any further command.
+ if self.data_channel:
+ self._quit_pending = True
+ self.sleeping = True
+ else:
+ self._shutdown_connecting_dtp()
+ self.close_when_done()
+ if self.username:
+ self.on_logout(self.username)
+
+ # --- data transferring
+
+ def ftp_LIST(self, path):
+ """Return a list of files in the specified directory to the
+ client.
+ """
+ # - If no argument, fall back on cwd as default.
+ # - Some older FTP clients erroneously issue /bin/ls-like LIST
+ # formats in which case we fall back on cwd as default.
+ try:
+ iterator = self.run_as_current_user(self.fs.get_list_dir, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ producer = BufferedIteratorProducer(iterator)
+ self.push_dtp_data(producer, isproducer=True, cmd="LIST")
+
+ def ftp_NLST(self, path):
+ """Return a list of files in the specified directory in a
+ compact form to the client.
+ """
+ try:
+ if self.fs.isdir(path):
+ listing = self.run_as_current_user(self.fs.listdir, path)
+ else:
+ # if path is a file we just list its name
+ self.fs.lstat(path) # raise exc in case of problems
+ listing = [os.path.basename(path)]
+ except OSError, err:
+ self.respond('550 %s.' % _strerror(err))
+ else:
+ data = ''
+ if listing:
+ listing.sort()
+ data = '\r\n'.join(listing) + '\r\n'
+ self.push_dtp_data(data, cmd="NLST")
+
+ # --- MLST and MLSD commands
+
+ # The MLST and MLSD commands are intended to standardize the file and
+ # directory information returned by the server-FTP process. These
+ # commands differ from the LIST command in that the format of the
+ # replies is strictly defined although extensible.
+
+ def ftp_MLST(self, path):
+ """Return information about a pathname in a machine-processable
+ form as defined in RFC-3659.
+ """
+ line = self.fs.fs2ftp(path)
+ basedir, basename = os.path.split(path)
+ perms = self.authorizer.get_perms(self.username)
+ try:
+ iterator = self.run_as_current_user(self.fs.format_mlsx, basedir,
+ [basename], perms, self._current_facts, ignore_err=False)
+ data = ''.join(iterator)
+ except OSError, err:
+ self.respond('550 %s.' % _strerror(err))
+ else:
+ # since TVFS is supported (see RFC-3659 chapter 6), a fully
+ # qualified pathname should be returned
+ data = data.split(' ')[0] + ' %s\r\n' % line
+ # response is expected on the command channel
+ 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.')
+
+ def ftp_MLSD(self, path):
+ """Return contents of a directory in a machine-processable form
+ as defined in RFC-3659.
+ """
+ # RFC-3659 requires 501 response code if path is not a directory
+ if not self.fs.isdir(path):
+ self.respond("501 No such directory.")
+ return
+ try:
+ listing = self.run_as_current_user(self.fs.listdir, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ perms = self.authorizer.get_perms(self.username)
+ iterator = self.fs.format_mlsx(path, listing, perms,
+ self._current_facts)
+ producer = BufferedIteratorProducer(iterator)
+ self.push_dtp_data(producer, isproducer=True, cmd="MLSD")
+
+ def ftp_RETR(self, file):
+ """Retrieve the specified file (transfer from the server to the
+ client)
+ """
+ rest_pos = self._restart_position
+ self._restart_position = 0
+ try:
+ fd = self.run_as_current_user(self.fs.open, file, 'rb')
+ except IOError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ return
+
+ if rest_pos:
+ # Make sure that the requested offset is valid (within the
+ # size of the file being resumed).
+ # According to RFC-1123 a 554 reply may result in case that
+ # the existing file cannot be repositioned as specified in
+ # the REST.
+ ok = 0
+ try:
+ if rest_pos > self.fs.getsize(file):
+ raise ValueError
+ fd.seek(rest_pos)
+ ok = 1
+ except ValueError:
+ why = "Invalid REST parameter"
+ except IOError, err:
+ why = _strerror(err)
+ if not ok:
+ self.respond('554 %s' % why)
+ return
+ producer = FileProducer(fd, self._current_type)
+ self.push_dtp_data(producer, isproducer=True, file=fd, cmd="RETR")
+
+ def ftp_STOR(self, file, mode='w'):
+ """Store a file (transfer from the client to the server)."""
+ # A resume could occur in case of APPE or REST commands.
+ # In that case we have to open file object in different ways:
+ # STOR: mode = 'w'
+ # APPE: mode = 'a'
+ # REST: mode = 'r+' (to permit seeking on file object)
+ if 'a' in mode:
+ cmd = 'APPE'
+ else:
+ cmd = 'STOR'
+ rest_pos = self._restart_position
+ self._restart_position = 0
+ if rest_pos:
+ mode = 'r+'
+ try:
+ fd = self.run_as_current_user(self.fs.open, file, mode + 'b')
+ except IOError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' %why)
+ return
+
+ if rest_pos:
+ # Make sure that the requested offset is valid (within the
+ # size of the file being resumed).
+ # According to RFC-1123 a 554 reply may result in case
+ # that the existing file cannot be repositioned as
+ # specified in the REST.
+ ok = 0
+ try:
+ if rest_pos > self.fs.getsize(file):
+ raise ValueError
+ fd.seek(rest_pos)
+ ok = 1
+ except ValueError:
+ why = "Invalid REST parameter"
+ except IOError, err:
+ why = _strerror(err)
+ if not ok:
+ self.respond('554 %s' %why)
+ return
+
+ if self.data_channel is not None:
+ resp = "Data connection already open. Transfer starting."
+ self.respond("125 " + resp)
+ self.data_channel.file_obj = fd
+ self.data_channel.enable_receiving(self._current_type, cmd)
+ else:
+ resp = "File status okay. About to open data connection."
+ self.respond("150 " + resp)
+ self._in_dtp_queue = (fd, cmd)
+
+
+ def ftp_STOU(self, line):
+ """Store a file on the server with a unique name."""
+ # Note 1: RFC-959 prohibited STOU parameters, but this
+ # prohibition is obsolete.
+ # Note 2: 250 response wanted by RFC-959 has been declared
+ # incorrect in RFC-1123 that wants 125/150 instead.
+ # Note 3: RFC-1123 also provided an exact output format
+ # defined to be as follow:
+ # > 125 FILE: pppp
+ # ...where pppp represents the unique path name of the
+ # file that will be written.
+
+ # watch for STOU preceded by REST, which makes no sense.
+ if self._restart_position:
+ self.respond("450 Can't STOU while REST request is pending.")
+ return
+
+ if line:
+ basedir, prefix = os.path.split(self.fs.ftp2fs(line))
+ prefix = prefix + '.'
+ else:
+ basedir = self.fs.ftp2fs(self.fs.cwd)
+ prefix = 'ftpd.'
+ try:
+ fd = self.run_as_current_user(self.fs.mkstemp, prefix=prefix,
+ dir=basedir)
+ except IOError, err:
+ # hitted the max number of tries to find out file with
+ # unique name
+ if err.errno == errno.EEXIST:
+ why = 'No usable unique file name found'
+ # something else happened
+ else:
+ why = _strerror(err)
+ self.respond("450 %s." % why)
+ return
+
+ if not self.authorizer.has_perm(self.username, 'w', fd.name):
+ try:
+ fd.close()
+ self.run_as_current_user(self.fs.remove, fd.name)
+ except OSError:
+ pass
+ self.respond("550 Not enough privileges.")
+ return
+
+ # now just acts like STOR except that restarting isn't allowed
+ filename = os.path.basename(fd.name)
+ if self.data_channel is not None:
+ self.respond("125 FILE: %s" % filename)
+ self.data_channel.file_obj = fd
+ self.data_channel.enable_receiving(self._current_type, "STOU")
+ else:
+ self.respond("150 FILE: %s" % filename)
+ self._in_dtp_queue = (fd, "STOU")
+
+ def ftp_APPE(self, file):
+ """Append data to an existing file on the server."""
+ # watch for APPE preceded by REST, which makes no sense.
+ if self._restart_position:
+ self.respond("450 Can't APPE while REST request is pending.")
+ else:
+ self.ftp_STOR(file, mode='a')
+
+ def ftp_REST(self, line):
+ """Restart a file transfer from a previous mark."""
+ if self._current_type == 'a':
+ self.respond('501 Resuming transfers not allowed in ASCII mode.')
+ return
+ try:
+ marker = int(line)
+ if marker < 0:
+ raise ValueError
+ except (ValueError, OverflowError):
+ self.respond("501 Invalid parameter.")
+ else:
+ self.respond("350 Restarting at position %s." % marker)
+ self._restart_position = marker
+
+ def ftp_ABOR(self, line):
+ """Abort the current data transfer."""
+ # ABOR received while no data channel exists
+ if (self._dtp_acceptor is None) and (self._dtp_connector is None) \
+ and (self.data_channel is None):
+ self.respond("225 No transfer to abort.")
+ return
+ else:
+ # a PASV or PORT was received but connection wasn't made yet
+ if self._dtp_acceptor is not None or self._dtp_connector is not None:
+ self._shutdown_connecting_dtp()
+ resp = "225 ABOR command successful; data channel closed."
+
+ # If a data transfer is in progress the server must first
+ # close the data connection, returning a 426 reply to
+ # indicate that the transfer terminated abnormally, then it
+ # must send a 226 reply, indicating that the abort command
+ # was successfully processed.
+ # If no data has been transmitted we just respond with 225
+ # indicating that no transfer was in progress.
+ if self.data_channel is not None:
+ if self.data_channel.transfer_in_progress():
+ self.data_channel.close()
+ self.data_channel = None
+ self.respond("426 Connection closed; transfer aborted.")
+ self.log("Transfer aborted via ABOR.")
+ resp = "226 ABOR command successful."
+ else:
+ self.data_channel.close()
+ self.data_channel = None
+ resp = "225 ABOR command successful; data channel closed."
+ self.respond(resp)
+
+
+ # --- authentication
+
+ def ftp_USER(self, line):
+ """Set the username for the current session."""
+ # RFC-959 specifies a 530 response to the USER command if the
+ # username is not valid. If the username is valid is required
+ # ftpd returns a 331 response instead. In order to prevent a
+ # malicious client from determining valid usernames on a server,
+ # it is suggested by RFC-2577 that a server always return 331 to
+ # the USER command and then reject the combination of username
+ # and password for an invalid username when PASS is provided later.
+ if not self.authenticated:
+ self.respond('331 Username ok, send password.')
+ else:
+ # a new USER command could be entered at any point in order
+ # to change the access control flushing any user, password,
+ # and account information already supplied and beginning the
+ # login sequence again.
+ self.flush_account()
+ msg = 'Previous account information was flushed'
+ self.log(msg)
+ self.respond('331 %s, send password.' % msg)
+ self.username = line
+
+ _auth_failed_timeout = 5
+
+ def ftp_PASS(self, line):
+ """Check username's password against the authorizer."""
+ if self.authenticated:
+ self.respond("503 User already authenticated.")
+ return
+ if not self.username:
+ self.respond("503 Login with USER first.")
+ return
+
+ def auth_failed(username, password, msg):
+ self.sleeping = False
+ if hasattr(self, '_closed') and not self._closed:
+ self.attempted_logins += 1
+ if self.attempted_logins >= self.max_login_attempts:
+ msg += " Disconnecting."
+ self.respond("530 " + msg)
+ self.close_when_done()
+ else:
+ self.respond("530 " + msg)
+ self.log_cmd("PASS", line, 530, msg)
+ self.on_login_failed(username, password)
+
+ if 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)
+ else:
+ self.push("230-%s\r\n" % msg_login)
+ self.respond("230 ")
+ self.authenticated = True
+ self.password = line
+ self.attempted_logins = 0
+
+ home = self.authorizer.get_home_dir(self.username)
+ self.fs = self.abstracted_fs(home, self)
+ self.on_login(self.username)
+ else:
+ self.sleeping = True
+ if self.username == 'anonymous':
+ msg = "Anonymous access not allowed."
+ else:
+ msg = "Authentication failed."
+ CallLater(self._auth_failed_timeout, auth_failed, self.username,
+ line, msg, _errback=self.handle_error)
+ self.username = ""
+
+ def ftp_REIN(self, line):
+ """Reinitialize user's current session."""
+ # From RFC-959:
+ # REIN command terminates a USER, flushing all I/O and account
+ # information, except to allow any transfer in progress to be
+ # completed. All parameters are reset to the default settings
+ # and the control connection is left open. This is identical
+ # to the state in which a user finds himself immediately after
+ # the control connection is opened.
+ self.log("Previous account information was flushed.")
+ self.flush_account()
+ # Note: RFC-959 erroneously mention "220" as the correct response
+ # code to be given in this case, but this is wrong...
+ self.respond("230 Ready for new user.")
+
+
+ # --- filesystem operations
+
+ def ftp_PWD(self, line):
+ """Return the name of the current working directory to the client."""
+ # The 257 response is supposed to include the directory
+ # name and in case it contains embedded double-quotes
+ # they must be doubled (see RFC-959, chapter 7, appendix 2).
+ self.respond('257 "%s" is the current directory.'
+ % self.fs.cwd.replace('"', '""'))
+
+ def ftp_CWD(self, path):
+ """Change the current working directory."""
+ try:
+ self.run_as_current_user(self.fs.chdir, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ self.respond('250 "%s" is the current directory.' % self.fs.cwd)
+
+ def ftp_CDUP(self, path):
+ """Change into the parent directory."""
+ # Note: RFC-959 says that code 200 is required but it also says
+ # that CDUP uses the same codes as CWD.
+ self.ftp_CWD(path)
+
+ def ftp_SIZE(self, path):
+ """Return size of file in a format suitable for using with
+ RESTart as defined in RFC-3659."""
+
+ # Implementation note: properly handling the SIZE command when
+ # TYPE ASCII is used would require to scan the entire file to
+ # perform the ASCII translation logic
+ # (file.read().replace(os.linesep, '\r\n')) and then calculating
+ # the len of such data which may be different than the actual
+ # size of the file on the server. Considering that calculating
+ # such result could be very resource-intensive and also dangerous
+ # (DoS) we reject SIZE when the current TYPE is ASCII.
+ # However, clients in general should not be resuming downloads
+ # in ASCII mode. Resuming downloads in binary mode is the
+ # recommended way as specified in RFC-3659.
+
+ line = self.fs.fs2ftp(path)
+ if self._current_type == 'a':
+ why = "SIZE not allowed in ASCII mode"
+ self.respond("550 %s." %why)
+ return
+ if not self.fs.isfile(self.fs.realpath(path)):
+ why = "%s is not retrievable" % line
+ self.respond("550 %s." % why)
+ return
+ try:
+ size = self.run_as_current_user(self.fs.getsize, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ self.respond("213 %s" % size)
+
+ def ftp_MDTM(self, path):
+ """Return last modification time of file to the client as an ISO
+ 3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659.
+ """
+ line = self.fs.fs2ftp(path)
+ if not self.fs.isfile(self.fs.realpath(path)):
+ self.respond("550 %s is not retrievable" % line)
+ return
+ if self.use_gmt_times:
+ timefunc = time.gmtime
+ else:
+ timefunc = time.localtime
+ try:
+ secs = self.run_as_current_user(self.fs.getmtime, path)
+ lmt = time.strftime("%Y%m%d%H%M%S", timefunc(secs))
+ except (OSError, ValueError), err:
+ if isinstance(err, OSError):
+ why = _strerror(err)
+ else:
+ # It could happen if file's last modification time
+ # happens to be too old (prior to year 1900)
+ why = "Can't determine file's last modification time"
+ self.respond('550 %s.' % why)
+ else:
+ self.respond("213 %s" % lmt)
+
+ def ftp_MKD(self, path):
+ """Create the specified directory."""
+ line = self.fs.fs2ftp(path)
+ try:
+ self.run_as_current_user(self.fs.mkdir, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' %why)
+ else:
+ # The 257 response is supposed to include the directory
+ # name and in case it contains embedded double-quotes
+ # they must be doubled (see RFC-959, chapter 7, appendix 2).
+ self.respond('257 "%s" directory created.' % line.replace('"', '""'))
+
+ def ftp_RMD(self, path):
+ """Remove the specified directory."""
+ if self.fs.realpath(path) == self.fs.realpath(self.fs.root):
+ msg = "Can't remove root directory."
+ self.respond("550 %s" % msg)
+ return
+ try:
+ self.run_as_current_user(self.fs.rmdir, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ self.respond("250 Directory removed.")
+
+ def ftp_DELE(self, path):
+ """Delete the specified file."""
+ try:
+ self.run_as_current_user(self.fs.remove, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ self.respond("250 File removed.")
+
+ def ftp_RNFR(self, path):
+ """Rename the specified (only the source name is specified
+ here, see RNTO command)"""
+ if not self.fs.lexists(path):
+ 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.")
+ else:
+ self._rnfr = path
+ self.respond("350 Ready for destination name.")
+
+ def ftp_RNTO(self, path):
+ """Rename file (destination name only, source is specified with
+ RNFR).
+ """
+ if not self._rnfr:
+ self.respond("503 Bad sequence of commands: use RNFR first.")
+ return
+ src = self._rnfr
+ self._rnfr = None
+ try:
+ self.run_as_current_user(self.fs.rename, src, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ self.respond("250 Renaming ok.")
+
+
+ # --- others
+
+ def ftp_TYPE(self, line):
+ """Set current type data type to binary/ascii"""
+ type = line.upper().replace(' ', '')
+ if type in ("A", "L7"):
+ self.respond("200 Type set to: ASCII.")
+ self._current_type = 'a'
+ elif type in ("I", "L8"):
+ self.respond("200 Type set to: Binary.")
+ self._current_type = 'i'
+ else:
+ self.respond('504 Unsupported type "%s".' % line)
+
+ def ftp_STRU(self, line):
+ """Set file structure ("F" is the only one supported (noop))."""
+ stru = line.upper()
+ if stru == 'F':
+ self.respond('200 File transfer structure set to: F.')
+ elif stru in ('P', 'R'):
+ # R is required in minimum implementations by RFC-959, 5.1.
+ # RFC-1123, 4.1.2.13, amends this to only apply to servers
+ # whose file systems support record structures, but also
+ # suggests that such a server "may still accept files with
+ # STRU R, recording the byte stream literally".
+ # Should we accept R but with no operational difference from
+ # F? proftpd and wu-ftpd don't accept STRU R. We just do
+ # the same.
+ #
+ # RFC-1123 recommends against implementing P.
+ self.respond('504 Unimplemented STRU type.')
+ else:
+ self.respond('501 Unrecognized STRU type.')
+
+ def ftp_MODE(self, line):
+ """Set data transfer mode ("S" is the only one supported (noop))."""
+ mode = line.upper()
+ if mode == 'S':
+ self.respond('200 Transfer mode set to: S')
+ elif mode in ('B', 'C'):
+ self.respond('504 Unimplemented MODE type.')
+ else:
+ self.respond('501 Unrecognized MODE type.')
+
+ def ftp_STAT(self, path):
+ """Return statistics about current ftp session. If an argument
+ is provided return directory listing over command channel.
+
+ Implementation note:
+
+ RFC-959 does not explicitly mention globbing but many FTP
+ servers do support it as a measure of convenience for FTP
+ clients and users.
+
+ In order to search for and match the given globbing expression,
+ the code has to search (possibly) many directories, examine
+ each contained filename, and build a list of matching files in
+ memory. Since this operation can be quite intensive, both CPU-
+ and memory-wise, we do not support globbing.
+ """
+ # return STATus information about ftpd
+ if not path:
+ s = []
+ s.append('Connected to: %s:%s' % self.socket.getsockname()[:2])
+ if self.authenticated:
+ s.append('Logged in as: %s' % self.username)
+ else:
+ if not self.username:
+ s.append("Waiting for username.")
+ else:
+ s.append("Waiting for password.")
+ if self._current_type == 'a':
+ type = 'ASCII'
+ else:
+ type = 'Binary'
+ s.append("TYPE: %s; STRUcture: File; MODE: Stream" % type)
+ if self._dtp_acceptor is not None:
+ s.append('Passive data channel waiting for connection.')
+ elif self.data_channel is not None:
+ bytes_sent = self.data_channel.tot_bytes_sent
+ bytes_recv = self.data_channel.tot_bytes_received
+ elapsed_time = self.data_channel.get_elapsed_time()
+ s.append('Data connection open:')
+ s.append('Total bytes sent: %s' % bytes_sent)
+ s.append('Total bytes received: %s' % bytes_recv)
+ s.append('Transfer elapsed time: %s secs' % elapsed_time)
+ 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.respond('211 End of status.')
+ # return directory LISTing over the command channel
+ else:
+ line = self.fs.fs2ftp(path)
+ try:
+ iterator = self.run_as_current_user(self.fs.get_list_dir, path)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' %why)
+ else:
+ self.push('213-Status of "%s":\r\n' % line)
+ self.push_with_producer(BufferedIteratorProducer(iterator))
+ self.respond('213 End of status.')
+
+ def ftp_FEAT(self, line):
+ """List all new features supported as defined in RFC-2398."""
+ features = ['TVFS']
+ features += [feat for feat in ('EPRT', 'EPSV', 'MDTM', 'SIZE') \
+ if feat in self.proto_cmds]
+ features.extend(self._extra_feats)
+ if 'MLST' in self.proto_cmds or 'MLSD' in self.proto_cmds:
+ facts = ''
+ for fact in self._available_facts:
+ if fact in self._current_facts:
+ facts += fact + '*;'
+ else:
+ facts += fact + ';'
+ features.append('MLST ' + facts)
+ if 'REST' in self.proto_cmds:
+ features.append('REST STREAM')
+ features.sort()
+ self.push("211-Features supported:\r\n")
+ self.push("".join([" %s\r\n" % x for x in features]))
+ self.respond('211 End FEAT.')
+
+ def ftp_OPTS(self, line):
+ """Specify options for FTP commands as specified in RFC-2389."""
+ try:
+ if line.count(' ') > 1:
+ raise ValueError('Invalid number of arguments')
+ if ' ' in line:
+ cmd, arg = line.split(' ')
+ if ';' not in arg:
+ raise ValueError('Invalid argument')
+ else:
+ cmd, arg = line, ''
+ # actually the only command able to accept options is MLST
+ if cmd.upper() != 'MLST' or 'MLST' not in self.proto_cmds:
+ raise ValueError('Unsupported command "%s"' % cmd)
+ except ValueError, 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]
+ f = ''.join([x + ';' for x in self._current_facts])
+ self.respond('200 MLST OPTS ' + f)
+
+ def ftp_NOOP(self, line):
+ """Do nothing."""
+ self.respond("200 I successfully done nothin'.")
+
+ def ftp_SYST(self, line):
+ """Return system type (always returns UNIX type: L8)."""
+ # This command is used to find out the type of operating system
+ # at the server. The reply shall have as its first word one of
+ # the system names listed in RFC-943.
+ # Since that we always return a "/bin/ls -lA"-like output on
+ # LIST we prefer to respond as if we would on Unix in any case.
+ self.respond("215 UNIX Type: L8")
+
+ def ftp_ALLO(self, line):
+ """Allocate bytes for storage (noop)."""
+ # not necessary (always respond with 202)
+ self.respond("202 No storage allocation necessary.")
+
+ def ftp_HELP(self, line):
+ """Return help text to the client."""
+ if line:
+ line = line.upper()
+ if line in self.proto_cmds:
+ self.respond("214 %s" % self.proto_cmds[line]['help'])
+ else:
+ self.respond("501 Unrecognized command.")
+ else:
+ # provide a compact list of recognized commands
+ def formatted_help():
+ cmds = []
+ keys = [x for x in self.proto_cmds.keys() if not x.startswith('SITE ')]
+ keys.sort()
+ while keys:
+ elems = tuple((keys[0:8]))
+ cmds.append(' %-6s' * len(elems) % elems + '\r\n')
+ del keys[0:8]
+ return ''.join(cmds)
+
+ self.push("214-The following commands are recognized:\r\n")
+ self.push(formatted_help())
+ self.respond("214 Help command successful.")
+
+ # --- site commands
+
+ # The user willing to add support for a specific SITE command must
+ # update self.proto_cmds dictionary and define a new ftp_SITE_%CMD%
+ # method in the subclass.
+
+ def ftp_SITE_CHMOD(self, path, mode):
+ """Change file mode."""
+ # Note: although most UNIX servers implement it, SITE CHMOD is not
+ # defined in any official RFC.
+ try:
+ assert len(mode) in (3, 4)
+ for x in mode:
+ assert 0 <= int(x) <= 7
+ mode = int(mode, 8)
+ except (AssertionError, ValueError):
+ self.respond("501 Invalid SITE CHMOD format.")
+ else:
+ try:
+ self.run_as_current_user(self.fs.chmod, path, mode)
+ except OSError, err:
+ why = _strerror(err)
+ self.respond('550 %s.' % why)
+ else:
+ self.respond('200 SITE CHMOD successful.')
+
+ def ftp_SITE_HELP(self, line):
+ """Return help text to the client for a given SITE command."""
+ if line:
+ line = line.upper()
+ if line in self.proto_cmds:
+ self.respond("214 %s" % self.proto_cmds[line]['help'])
+ else:
+ self.respond("501 Unrecognized SITE command.")
+ else:
+ self.push("214-The following SITE commands are recognized:\r\n")
+ site_cmds = []
+ keys = self.proto_cmds.keys()
+ keys.sort()
+ for cmd in keys:
+ if cmd.startswith('SITE '):
+ site_cmds.append(' %s\r\n' % cmd[5:])
+ self.push(''.join(site_cmds))
+ self.respond("214 Help SITE command successful.")
+
+ # --- support for deprecated cmds
+
+ # RFC-1123 requires that the server treat XCUP, XCWD, XMKD, XPWD
+ # and XRMD commands as synonyms for CDUP, CWD, MKD, LIST and RMD.
+ # Such commands are obsoleted but some ftp clients (e.g. Windows
+ # ftp.exe) still use them.
+
+ def ftp_XCUP(self, line):
+ """Change to the parent directory. Synonym for CDUP. Deprecated."""
+ self.ftp_CDUP(line)
+
+ def ftp_XCWD(self, line):
+ """Change the current working directory. Synonym for CWD. Deprecated."""
+ self.ftp_CWD(line)
+
+ def ftp_XMKD(self, line):
+ """Create the specified directory. Synonym for MKD. Deprecated."""
+ self.ftp_MKD(line)
+
+ def ftp_XPWD(self, line):
+ """Return the current working directory. Synonym for PWD. Deprecated."""
+ self.ftp_PWD(line)
+
+ def ftp_XRMD(self, line):
+ """Remove the specified directory. Synonym for RMD. Deprecated."""
+ self.ftp_RMD(line)
+
+
+class FTPServer(object, asyncore.dispatcher):
+ """This class is an asyncore.disptacher subclass. It creates a FTP
+ socket listening on <address>, dispatching the requests to a <handler>
+ (typically FTPHandler class).
+
+ Depending on the type of address specified IPv4 or IPv6 connections
+ (or both, depending from the underlying system) will be accepted.
+
+ All relevant session information is stored in class attributes
+ described below.
+
+ - (int) max_cons:
+ number of maximum simultaneous connections accepted (defaults
+ to 512). Can be set to 0 for unlimited but it is recommended
+ to always have a limit to avoid running out of file descriptors
+ (DoS).
+
+ - (int) max_cons_per_ip:
+ number of maximum connections accepted for the same IP address
+ (defaults to 0 == unlimited).
+ """
+
+ max_cons = 512
+ max_cons_per_ip = 0
+
+ def __init__(self, address, handler):
+ """Initiate the FTP server opening listening on address.
+
+ - (tuple) address: the host:port pair on which the command
+ channel will listen.
+
+ - (classobj) handler: the handler class to use.
+ """
+ asyncore.dispatcher.__init__(self)
+ self.handler = handler
+ self.ip_map = []
+ host, port = address
+ # in case of FTPS class not properly configured we want errors
+ # to be raised here rather than later, when client connects
+ if hasattr(handler, 'get_ssl_context'):
+ handler.get_ssl_context()
+
+ # AF_INET or AF_INET6 socket
+ # Get the correct address family for our host (allows IPv6 addresses)
+ try:
+ info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
+ except socket.gaierror:
+ # Probably a DNS issue. Assume IPv4.
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind((host, port))
+ else:
+ for res in info:
+ af, socktype, proto, canonname, sa = res
+ try:
+ self.create_socket(af, socktype)
+ self.set_reuse_addr()
+ self.bind(sa)
+ except socket.error, msg:
+ if self.socket:
+ self.socket.close()
+ self.socket = None
+ continue
+ break
+ if not self.socket:
+ raise socket.error(msg)
+ self.listen(5)
+
+ @property
+ def address(self):
+ return self.socket.getsockname()[:2]
+
+ def set_reuse_addr(self):
+ # Overridden for convenience. Avoid to reuse address on Windows.
+ if (os.name in ('nt', 'ce')) or (sys.platform == 'cygwin'):
+ return
+ asyncore.dispatcher.set_reuse_addr(self)
+
+ @classmethod
+ def serve_forever(cls, timeout=1.0, use_poll=False, count=None):
+ """A wrap around asyncore.loop(); starts the asyncore polling
+ loop including running the scheduler.
+ The arguments are the same expected by original asyncore.loop()
+ function:
+
+ - (float) timeout: the timeout passed to select() or poll()
+ system calls expressed in seconds (default 1.0).
+
+ - (bool) use_poll: when True use poll() instead of select()
+ (default False).
+
+ - (int) count: how many times the polling loop gets called
+ before returning. If None loops forever (default None).
+ """
+ if use_poll and hasattr(asyncore.select, 'poll'):
+ poll_fun = asyncore.poll2
+ else:
+ poll_fun = asyncore.poll
+
+ if count is None:
+ log("starting FTP server")
+ try:
+ try:
+ while asyncore.socket_map or _scheduler._tasks:
+ poll_fun(timeout)
+ _scheduler()
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ pass
+ finally:
+ log("shutting down FTP server")
+ cls.close_all()
+ else:
+ while (asyncore.socket_map or _scheduler._tasks) and count > 0:
+ if asyncore.socket_map:
+ poll_fun(timeout)
+ if _scheduler._tasks:
+ _scheduler()
+ count -= 1
+
+ def handle_accept(self):
+ """Called when remote client initiates a connection."""
+ try:
+ sock, addr = self.accept()
+ except TypeError:
+ # sometimes accept() might return None (see issue 91)
+ return
+ except socket.error, err:
+ # ECONNABORTED might be thrown on *BSD (see issue 105)
+ if err.args[0] != errno.ECONNABORTED:
+ logerror(traceback.format_exc())
+ return
+ else:
+ # sometimes addr == None instead of (ip, port) (see issue 104)
+ if addr is None:
+ return
+
+ handler = None
+ ip = None
+ try:
+ handler = self.handler(sock, self)
+ if not handler.connected:
+ return
+ log("[]%s:%s Connected." % addr[:2])
+ ip = addr[0]
+ self.ip_map.append(ip)
+
+ # For performance and security reasons we should always set a
+ # limit for the number of file descriptors that socket_map
+ # should contain. When we're running out of such limit we'll
+ # use the last available channel for sending a 421 response
+ # to the client before disconnecting it.
+ if self.max_cons and (len(asyncore.socket_map) > self.max_cons):
+ handler.handle_max_cons()
+ return
+
+ # accept only a limited number of connections from the same
+ # source address.
+ if self.max_cons_per_ip:
+ if self.ip_map.count(ip) > self.max_cons_per_ip:
+ handler.handle_max_cons_per_ip()
+ return
+
+ try:
+ handler.handle()
+ except:
+ handler.handle_error()
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except:
+ # This is supposed to be an application bug that should
+ # be fixed. We do not want to tear down the server though
+ # (DoS). We just log the exception, hoping that someone
+ # will eventually file a bug. References:
+ # - http://code.google.com/p/pyftpdlib/issues/detail?id=143
+ # - http://code.google.com/p/pyftpdlib/issues/detail?id=166
+ # - https://groups.google.com/forum/#!topic/pyftpdlib/h7pPybzAx14
+ logerror(traceback.format_exc())
+ if handler is not None:
+ handler.close()
+ else:
+ if ip is not None and ip in self.ip_map:
+ self.ip_map.remove(ip)
+
+ def writable(self):
+ return 0
+
+ def handle_error(self):
+ """Called to handle any uncaught exceptions."""
+ try:
+ raise
+ except (KeyboardInterrupt, SystemExit, asyncore.ExitNow):
+ raise
+ except:
+ logerror(traceback.format_exc())
+ self.close()
+
+ @classmethod
+ def close_all(cls, ignore_all=False):
+ """Stop serving and also disconnects all currently connected
+ clients.
+
+ - (bool) ignore_all:
+ having it set to False results in raising exception in case
+ of unexpected errors.
+
+ Implementation note:
+
+ This is how asyncore.close_all() is implemented starting from
+ Python 2.6.
+ The previous versions of close_all() instead of iterating over
+ all opened channels and calling close() method for each one
+ of them only closed sockets generating memory leaks.
+ """
+ values = asyncore.socket_map.values()
+ # We sort the list so that we close all FTP handler instances
+ # first since FTPHandler.close() has the peculiarity of
+ # automatically closing all its children (DTPHandler, ActiveDTP
+ # and PassiveDTP).
+ # This should minimize the possibility to incur in race
+ # conditions or memory leaks caused by orphaned references
+ # left behind in case of error.
+ values.sort(key=lambda inst: isinstance(inst, FTPHandler), reverse=True)
+ for x in values:
+ try:
+ x.close()
+ except OSError, x:
+ if x[0] == errno.EBADF:
+ pass
+ elif not ignore_all:
+ raise
+ except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ if not ignore_all:
+ asyncore.socket_map.clear()
+ del _scheduler._tasks[:]
+ raise
+ asyncore.socket_map.clear()
+
+ for x in _scheduler._tasks:
+ try:
+ if not x.cancelled:
+ x.cancel()
+ except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ if not ignore_all:
+ del _scheduler._tasks[:]
+ raise
+ del _scheduler._tasks[:]
+
+
+def main():
+ """Start a stand alone anonymous FTP server."""
+
+ class CustomizedOptionFormatter(optparse.IndentedHelpFormatter):
+ """Formats options shown in help in a prettier way."""
+
+ def format_option(self, option):
+ result = []
+ opts = self.option_strings[option]
+ result.append(' %s\n' % opts)
+ if option.help:
+ help_text = ' %s\n\n' % self.expand_default(option)
+ result.append(help_text)
+ return ''.join(result)
+
+ usage = "python -m pyftpdlib.ftpserver [options]"
+ parser = optparse.OptionParser(usage=usage, description=main.__doc__,
+ formatter=CustomizedOptionFormatter())
+ parser.add_option('-i', '--interface', default='0.0.0.0', metavar="ADDRESS",
+ help="specify the interface to run on (default all "
+ "interfaces)")
+ parser.add_option('-p', '--port', type="int", default=21, metavar="PORT",
+ help="specity port number to run on (default 21)")
+ parser.add_option('-w', '--write', action="store_true", default=False,
+ help="grants write access for the anonymous user "
+ "(default read-only)")
+ parser.add_option('-d', '--directory', default=os.getcwd(), metavar="FOLDER",
+ help="specify the directory to share (default current "
+ "directory)")
+ parser.add_option('-n', '--nat-address', default=None, metavar="ADDRESS",
+ help="the NAT address to use for passive connections")
+ parser.add_option('-r', '--range', default=None, metavar="FROM-TO",
+ help="the range of TCP ports to use for passive "
+ "connections (e.g. -r 8000-9000)")
+ parser.add_option('-v', '--version', action='store_true',
+ help="print pyftpdlib version and exit")
+
+ options, args = parser.parse_args()
+ if options.version:
+ sys.exit("pyftpdlib %s" % __ver__)
+ passive_ports = None
+ if options.range:
+ try:
+ start, stop = options.range.split('-')
+ start = int(start)
+ stop = int(stop)
+ except ValueError:
+ parser.error('invalid argument passed to -r option')
+ else:
+ passive_ports = range(start, stop + 1)
+ # On recent Windows versions, if address is not specified and IPv6
+ # is installed the socket will listen on IPv6 by default; in this
+ # case we force IPv4 instead.
+ if os.name in ('nt', 'ce') and not options.interface:
+ options.interface = '0.0.0.0'
+
+ authorizer = DummyAuthorizer()
+ perm = options.write and "elradfmwM" or "elr"
+ authorizer.add_anonymous(options.directory, perm=perm)
+ handler = FTPHandler
+ handler.authorizer = authorizer
+ handler.masquerade_address = options.nat_address
+ handler.passive_ports = passive_ports
+ ftpd = FTPServer((options.interface, options.port), FTPHandler)
+ ftpd.serve_forever()
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/third_party/pyftpdlib/src/setup.py b/chromium/third_party/pyftpdlib/src/setup.py
new file mode 100644
index 00000000000..b3f43dc83e0
--- /dev/null
+++ b/chromium/third_party/pyftpdlib/src/setup.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# $Id$
+#
+# pyftpdlib is released under the MIT license, reproduced below:
+# ======================================================================
+# Copyright (C) 2007-2012 Giampaolo Rodola' <g.rodola@gmail.com>
+#
+# All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# ======================================================================
+
+"""pyftpdlib installer.
+
+To install pyftpdlib just open a command shell and run:
+> python setup.py install
+"""
+
+import os
+import sys
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+name = 'pyftpdlib'
+version = '0.7.0'
+download_url = "http://pyftpdlib.googlecode.com/files/" + name + "-" + \
+ version + ".tar.gz"
+
+setup(
+ name=name,
+ version=version,
+ description='High-level asynchronous FTP server library',
+ long_description="Python FTP server library provides an high-level portable "
+ "interface to easily write asynchronous FTP servers with "
+ "Python.",
+ license='License :: OSI Approved :: MIT License',
+ platforms='Platform Independent',
+ author="Giampaolo Rodola'",
+ author_email='g.rodola@gmail.com',
+ url='http://code.google.com/p/pyftpdlib/',
+ download_url=download_url,
+ packages=['pyftpdlib', 'pyftpdlib/contrib'],
+ keywords=['ftp', 'ftps', 'server', 'ftpd', 'daemon', 'python', 'ssl',
+ 'sendfile', 'rfc959', 'rfc1123', 'rfc2228', 'rfc2428', 'rfc3659'],
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: File Transfer Protocol (FTP)',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: System :: Filesystems',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.4',
+ 'Programming Language :: Python :: 2.5',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ ],
+ )
+
+if os.name == 'posix':
+ try:
+ import sendfile
+ except ImportError:
+ msg = "\nYou might want to install pysendfile module to speedup " \
+ "transfers:\nhttp://code.google.com/p/pysendfile/\n"
+ if sys.stderr.isatty():
+ sys.stderr.write('\x1b[1m%s\x1b[0m' % msg)
+ else:
+ sys.stderr.write(msg)