Tutorial

Tutorial Tutorial containing API reference and example usages # Table of contents¶ Table of contents 1.0 - Introduction 2.0 - API reference 2.1 - Contrib package 2.2 - pyftpdlib.contrib.handlers module 2.3 - pyftpdlib.contrib.authorizers module 2.4 - pyftpdlib.contrib.filesystems module 3.0 - Customizing your FTP server 3.1 - Building a Base FTP server 3.2 - Logging management 3.3 - Storing passwords as hash digests 3.4 - Unix FTP Server 3.5 - Windows NT FTP Server 3.6 - Throttle bandwidth 3.7 - FTPS (FTP over TLS/SSL) server 3.8 - Event callbacks 3.9 - Command line usage # 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. This document is intended to serve as a simple API reference of most important classes and functions. Also included is an introduction to customization through the use of some example scripts. Some of them are included in demo directory of pyftpdlib source distribution. 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')
add_user`(`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 insufficient permissions or duplicate usernames. Optional `perm` 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: Read permissions: "e" = change directory (CWD command) "l" = list files (LIST, NLST, STAT, MLSD, MLST 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) 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. add_anonymous`(`homedir`[`, `**`kwargs`])` Add an anonymous user to the virtual users table. `AuthorizerError` exception raised on error conditions such as insufficient 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 permission. Using a "write" value results in a `RuntimeWarning`. override_perm`(`directory, perm`[`, recursive=False`])` Override permissions for a given directory. New in version 0.5.0 validate_authentication`(`username, password`)` Return `True` if the supplied `username` and `password` match the stored credentials. impersonate_user`(`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. terminate_impersonation`(`username, password`)` 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. remove_user`(`username`)` Remove a user from the virtual user table. 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: 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). New in version 5.0 banner String sent when client connects (default `"pyftpdlib %s ready." %__ver__`). max_login_attempts Maximum number of wrong authentications before disconnecting (default `3`). permit_foreign_addresses Whether enable FXP feature (default `False`). permit_privileged_ports Set to `True` if you want to permit active connections (PORT) over privileged ports (not recommended, default `False`). 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`). 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 (defaults to `{}`). New in version 0.6.0 passive_ports What ports 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`). use_gmt_times When True causes the server to report all ls and MDTM times in GMT and not local time (default `True`). New in version 0.6.0 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 platforms where it is supported). New in version 0.6.0 Follows a list of callback methods that can be overridden in a subclass. For blocking operations read the FAQ on how to run time consuming tasks on_login`(`username`)` Called on user login. New in version 0.6.0 on_file_sent`(`file`)` Called every time a file has been successfully sent. New in version 0.5.1 on_file_received`(`file`)` Called every time a file has been successfully received. New in version 0.5.1 on_incomplete_file_sent`(`file`)` Called every time a file has not been entirely sent (e.g. transfer aborted via ABOR or client disconnected abruptly). `file` is the absolute name of that file. New in version 0.6.0 on_incomplete_file_received`(`file`)` Called every time a file has not been entirely received (e.g. transfer aborted via ABOR or client disconnected abruptly). `file` is the absolute name of that file. New in version 0.6.0 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. 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. New in version 5.0 ac_in_buffer_size ac_out_buffer_size The buffer sizes to use when receiving and sending data (both defaulting to `65536` 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. 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 read_limit The maximum number of bytes to read (receive) in one second (defaults to 0 == no limit) write_limit The maximum number of bytes to write (send) in one second (defaults to 0 == no limit). 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()
max_cons Number of maximum simultaneous connections accepted (default `512`). max_cons_per_ip Number of maximum connections accepted for the same IP address (default `0` == no limit). serve_forever(`[`timeout=1`[`, use_poll=False`[`, map=None`[`, 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. close`()` Stop serving without disconnecting currently connected clients. close_all(`[`map=None`[`, ignore_all=False`]])` Stop serving disconnecting also the currently connected clients. The `map` parameter is a dictionary whose items are the channels to close. If `map` is omitted, the default `asyncore.socket_map` is used. Having `ignore_all` parameter set to `False` results in raising exception in case of unexpected errors. 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. `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. The contructor accepts two arguments: `root` which is the user "real" home directory (e.g. '/home/user') and `cmd_channel` which is the `FTPHandler` class instance. Changed in version 0.6.0: `root` and `cmd_channel` arguments were added. root User's home directory ("real"). cwd User's current working directory ("virtual"). ftpnorm`(`ftppath`)` Normalize a "virtual" ftp pathname depending on the current working directory (e.g. having "/foo" as current working directory "x" becomes "/foo/x"). ftp2fs`(`ftppath`)` Translate a "virtual" ftp pathname into equivalent absolute "real" filesystem pathname (e.g. having "/home/user" as root directory "x" becomes "/home/user/x"). fs2ftp`(`fspath`)` 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". validpath`(`path`)` 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 `False`). # 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: certfile 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. keyfile The path of the file containing the private RSA key; can be omittetted if certfile already contains the private key (defaults: `None`). 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`). 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`). ## 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`. 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. The user home directories will be automatically determined when user logins (e.g. /home/user on Unix, C:\Documents and settings\user on Windows). 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. 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). Note that in order to use this class super user privileges are required. New in version 0.6.0 override_user(username=None, password=None, homedir=None, perm=None, anonymous_user=None, msg_login=None, msg_quit=None) Overrides one or more options specified in the class constructor for a specific user. 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. Also requires_valid_shell option is not available. In order to use this class pywin32 extension must be installed. 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. download script
    #!/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. download script
    #!/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. download script
    #!/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. download script
    #!/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.