Tutorial
Tutorial containing API reference and example usages
Table of contents¶
1.0 - Introduction¶pyftpdlib implements the server side of the FTP protocol as defined in RFC-959. pyftpdlib consist of a single file, ftpserver.py, which contains a hierarchy of classes, functions and variables which implement the backend functionality for the ftpd. 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. 2.0 - API reference¶function pyftpdlib.ftpserver.log(msg) Log messages intended for the end user. function pyftpdlib.ftpserver.logline(msg) Log commands and responses passing through the command channel. function pyftpdlib.ftpserver.logerror(msg) Log traceback outputs occurring in case of errors. class pyftpdlib.ftpserver.AuthorizerError() Base class for authorizers exceptions. class pyftpdlib.ftpserver.DummyAuthorizer() 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 filesystem 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. Typically the first thing you have to do is create an instance of this class and start adding ftp users: >>> from pyftpdlib import ftpserver >>> authorizer = ftpserver.DummyAuthorizer() >>> authorizer.add_user('user', 'password', '/home/user', perm='elradfmw') >>> authorizer.add_anonymous('/home/nobody')
Read permissions: Optional msg_login and msg_quit arguments can be specified to provide customized response strings when user log-in and quit. The perm argument of the add_user() 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.
class pyftpdlib.ftpserver.FTPHandler(conn, server) This class implements the FTP server Protocol Interpreter (see RFC-959), handling commands received from the client on the control channel by calling the command's corresponding method (e.g. for received command "MKD pathname", ftp_MKD() method is called with pathname as the argument). All relevant session information are stored in instance variables. conn is the underlying socket object instance of the newly established connection, server is the FTPServer class instance. Basic usage simply requires creating an instance of FTPHandler class and specify which authorizer instance it will going to use: >>> ftp_handler = ftpserver.FTPHandler >>> ftp_handler.authorizer = authorizer All relevant session information is stored in class attributes reproduced below and can be modified before instantiating this class:
class pyftpdlib.ftpserver.DTPHandler(sock_obj, cmd_channel) This class handles the server-data-transfer-process (server-DTP, see RFC-959) managing all transfer operations regarding the data channel. sock_obj is the underlying socket object instance of the newly established connection, cmd_channel is the FTPHandler class instance.
class pyftpdlib.ftpserver.ThrottledDTPHandler(sock_obj, cmd_channel) 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. Use it instead of DTPHandler to set transfer rates limits for both downloads and/or uploads (see the demo script showing the example usage). New in version 0.5.2
class pyftpdlib.ftpserver.FTPServer(address, handler) This class is an asyncore.dispatcher subclass. It creates a FTP socket listening on address (a tuple containing the ip:port pair), dispatching the requests to a "handler" (typically FTPHandler class object). It is typically used for starting asyncore polling loop: >>> address = ('127.0.0.1', 21) >>> ftpd = ftpserver.FTPServer(address, ftp_handler) >>> ftpd.serve_forever()
class pyftpdlib.ftpserver.AbstractedFS(root, cmd_channel) 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. Changed in version 0.6.0: root and cmd_channel arguments were added.
2.1 - Contrib package¶Starting from version 0.6.0 a new contrib package has been added to pyftpdlib namespace which extends base ftpserver.py 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. 2.2 - pyftpdlib.contrib.handlers module¶This module provides basic support for FTPS (FTP over SSL/TLS) as described in RFC-4217 implementing AUTH, PBSZ and PROT commands. In order to make it work PyOpenSSL module is required to be installed. Example below shows how to setup an FTPS server. class pyftpdlib.contrib.handlers.TLS_FTPHandler(conn, server) A ftpserver.FTPHandler subclass supporting TLS/SSL. Configurable attributes:
2.3 - pyftpdlib.contrib.authorizers module¶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 DummyAuthorizer. class pyftpdlib.contrib.authorizers.UnixAuthorizer(global_perm="elradfmw", allowed_users=[], rejected_users=[], require_valid_shell=True, anonymous_user=None, ,msg_login="Login successful.", msg_quit="Goodbye.") global_perm is a series of letters referencing the users permissions; defaults to "elradfmw" which means full read and write access for everybody (except anonymous). allowed_users and rejected_users options expect a list of users which are accepted or rejected for authenticating against the FTP server; defaults both to [] (no restrictions). 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). anonymous_user 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 None (anonymous access disabled).
New in version 0.6.0
Examples: >>> 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") class pyftpdlib.contrib.authorizers.WindowsAuthorizer(global_perm="elradfmw", allowed_users=[], rejected_users=[], anonymous_user=None, anonymous_password="", msg_login="Login successful.", msg_quit="Goodbye."): Same as UnixAuthorizer except for anonymous_password argument which must be specified when defining the anonymous_user. New in version 0.6.0
2.4 - pyftpdlib.contrib.filesystems module¶class pyftpdlib.contrib.filesystems.UnixFilesystem(root, cmd_channel) 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. Use it in conjuction with UnixAuthorizer to implement a "real" UNIX FTP server (see demo/unix_ftpd.py). New in version 0.6.0 3.0 - Customizing your FTP server¶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 demo directory of pyftpdlib source distribution. 3.1 - Building a Base FTP server¶The script below is a basic configuration, and it's probably the best starting point for understanding how things work. It uses the base DummyAuthorizer for adding a bunch of "virtual" users. It also sets a limit for connections by overriding FTPServer.max_cons and FTPServer.max_cons_per_ip 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 0, or "no limit") since they are a good workaround for avoiding DoS attacks. #!/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() 3.2 - Logging management¶As mentioned, ftpserver.py comes with 3 different functions intended for a separate logging system: log(), logline() and logerror(). Let's suppose you don't want to print FTPd messages on screen but you want to write them into different files: "/var/log/ftpd.log" will be main log file, "/var/log/ftpd.lines.log" the one where you'll want to store commands and responses passing through the control connection. Here's one method this could be implemented: #!/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() 3.3 - Storing passwords as hash digests¶Using FTP server library with the default DummyAuthorizer 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. 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. 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 hashlib module included with Python stdlib and by sub-classing the original DummyAuthorizer class overriding its validate_authentication() method. #!/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 < 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() 3.4 - Unix FTP Server¶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. The example below uses UnixAuthorizer and UnixFilesystem classes to do so. #!/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() 3.5 - Windows NT FTP Server¶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 pywin32 extension to be installed. #!/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() 3.6 - Throttle bandwidth¶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 ThrottledDTPHandler class to set such limits. The basic idea behind ThrottledDTPHandler 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. See throttled_ftpd.py demo script providing an example on how to use it. 3.7 - FTPS (FTP over TLS/SSL) server¶Starting from version 0.6.0 pyftpdlib finally includes full FTPS support implementing both TLS and SSL protocols and AUTH, PBSZ and PROT commands as defined in RFC-4217. This has been implemented by using PyOpenSSL module, which is required in order to run the code below. TLS_FTPHandlerFactory class requires a certfile and a keyfile to be specified. Apache FAQs 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 here. #!/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() 3.8 - Event callbacks¶A small example which shows how to use callback methods via FTPHandler subclassing: 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() 3.9 - Command line usage¶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. Anonymous FTPd sharing current directory: python -m pyftpdlib.ftpserver Anonymous FTPd with write permission: python -m pyftpdlib.ftpserver -w Set a different address/port and home directory: python -m pyftpdlib.ftpserver -i localhost -p 8021 -d /home/someone See python -m pyftpdlib.ftpserver -h for a complete list of options. |