1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
|
====
FAQs
====
.. contents:: Table of Contents
Introduction
============
What is pyftpdlib?
------------------
pyftpdlib is a high-level library to easily write asynchronous portable FTP
servers with `Python`_.
I'm not a python programmer. Can I use it anyway?
-------------------------------------------------
Yes. Pyftpdlib is a fully working FTP server implementation that can be run
"as is". For example, you could run an anonymous FTP server with write access
from command line by running:
.. code-block:: sh
$ python3 -m pyftpdlib -w
RuntimeWarning: write permissions assigned to anonymous user.
[I 13-02-20 14:16:36] >>> starting FTP server on 0.0.0.0:2121 <<<
[I 13-02-20 14:16:36] poller: <class 'pyftpdlib.ioloop.Epoll'>
[I 13-02-20 14:16:36] masquerade (NAT) address: None
[I 13-02-20 14:16:36] passive ports: None
[I 13-02-20 14:16:36] use sendfile(2): True
This is useful in case you want a quick and dirty way to share a directory
without, say, installing and configuring Samba.
Installing and compatibility
============================
How do I install pyftpdlib?
---------------------------
.. code-block:: sh
$ python3 -m pip install pyftpdlib
Also see `install instructions`_.
Which Python versions are compatible?
-------------------------------------
Python *3.X*. Anything above 3.8 should be good to go. Pypy should also work.
What about Python 2.7?
----------------------
Latest pyftpdlib version supporting Python 2.7 is 1.5.10. You can install it
with:
.. code-block:: sh
python2 -m pip install pyftpdlib==1.5.10
On which platforms can pyftpdlib be used?
-----------------------------------------
pyftpdlib should work on any platform where **select()**, **poll()**,
**epoll()** or **kqueue()** system calls are available, namely UNIX and
Windows.
Usage
=====
How can I run long-running tasks without blocking the server?
-------------------------------------------------------------
pyftpdlib is an *asynchronous* FTP server. That means that if you need to run a
time consuming task you have to use a separate Python process or thread,
otherwise the entire asynchronous loop will be blocked.
Let's suppose you want to implement a long-running task every time the server
receives a file. The code snippet below shows how.
With ``self.del_channel()`` we temporarily "sleep" the connection handler which
will be removed from the async IO poller loop and won't be able to send or
receive any more data. It won't be closed (disconnected) as long as we don't
invoke ``self.add_channel()``. This is fundamental when working with threads to
avoid race conditions, dead locks etc.
.. code-block:: python
class MyHandler(FTPHandler):
def on_file_received(self, file):
def blocking_task():
time.sleep(5)
self.add_channel()
self.del_channel()
threading.Thread(target=blocking_task).start()
Another possibility is to `change the default concurrency model`_.
Why do I get "Permission denied" error on startup?
--------------------------------------------------
Probably because you're on a UNIX system and you're trying to start the FTP
server as an unprivileged user. FTP servers bind on port 21 by default, and
only the root user can bind sockets on such ports. If you want to bind the
socket as non-privileged user you should set a port higher than 1024.
Can I control upload/download ratios?
-------------------------------------
Yes. Pyftpdlib provides a new class called `ThrottledDTPHandler`_. You can set
speed limits by modifying `ThrottledDTPHandler.read_limit`_ and
`ThrottledDTPHandler.write_limit`_ class attributes as it is shown in
`demo/throttled_ftpd.py`_ script.
Are there ways to limit connections?
------------------------------------
The `FTPServer`_. class comes with two overridable attributes defaulting to
zero (no limit): `FTPServer.max_cons`_, which sets a limit for maximum
simultaneous connection to handle, and `FTPServer.max_cons_per_ip`_ which sets
a limit for the connections from the same IP address.
I'm behind a NAT / gateway
--------------------------
The FTP protocol uses 2 TCP connections: a "control" connection to exchange
protocol messages (LIST, RETR, etc.), and a "data" connection for transfering
data (files). In order to open the data connection the FTP server must
communicate its **public** IP address in the PASV response. If you're behind a
NAT, this address must be explicitly configured by setting the
`FTPHandler.masquerade_address`_ attribute.
You can get your public IP address by using services like
https://www.whatismyip.com/.
In addition, you also probably want to configure a given range of TCP ports for
such incoming "data" connections, otherwise a random TCP port will be picked up
every time. You can do so by using the `FTPHandler.passive_ports`_ attribute.
The value expected by `FTPHandler.passive_ports`_ attribute is a list of
integers (e.g. ``range(60000, 65535)``).
This also means that you must configure your router so the it will forward the
incoming connections to such TCP ports from the router to your FTP server
behind the NAT.
Why timestamps shown by MDTM and ls commands (LIST, MLSD, MLST) are wrong?
--------------------------------------------------------------------------
If by "wrong" you mean "different from the timestamp of that file on my client
machine", then that is the expected behavior. pyftpdlib uses `GMT times`_ as
recommended in `RFC-3659`_. Any client complying with RFC-3659 should be able
to convert the GMT time to your local time and show the correct timestamp. In
case you want LIST, MLSD, MLST commands to report local times instead, just set
the `FTPHandler.use_gmt_times`_ attribute to ``False``. For further information
you might want to take a look at
http://www.proftpd.org/docs/howto/Timestamps.html.
Implementation
==============
sendfile()
----------
On Linux, and only when doing transfer in clear text (aka no FTPS), the
``sendfile(2)`` system call be used when uploading files (from server to
client) via RETR command. Using ``sendfile(2)`` is more efficient, and usually
results in transfer rates that are from 2x to 3x faster.
In the past some cases were reported that using ``sendfile(2)`` with "non
regular" filesystems such as NFS, SMBFS/Samba, CIFS or network mounts in
general may cause some issues, see
http://www.proftpd.org/docs/howto/Sendfile.html. If you bump into one these
issues you can set `FTPHandler.use_sendfile`_ to ``False``:
.. code-block:: python
from pyftpdlib.handlers import FTPHandler
handler = FTPHandler
handler.use_senfile = False
...
Globbing / STAT command implementation
--------------------------------------
Globbing is a common UNIX shell mechanism for expanding wildcard patterns to
match multiple filenames. When an argument is provided to the *STAT* command,
the FTP server should return a directory listing over the command channel.
`RFC-959`_ 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 (and slow) pyftpdlib *does not* support globbing.
ASCII transfers / SIZE command implementation
---------------------------------------------
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 length of
such data which may be different than the actual size of the file on the
server. Considering that calculating such a result could be resource-intensive,
it could be easy for a malicious client to use this as a DoS attack. As such
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 `RFC-3659`_.
IPv6 support
------------
Pyftpdlib does support IPv6 (`RFC-2428`_). If you want your FTP server to
explicitly use IPv6 you can do so by passing a valid IPv6 address to the
`FTPServer`_ class constructor. Example:
.. code-block:: python
>>> from pyftpdlib.servers import FTPServer
>>> address = ("::1", 21) # listen on localhost, port 21
>>> ftpd = FTPServer(address, FTPHandler)
>>> ftpd.serve_forever()
Serving FTP on ::1:21
If the OS supports an hybrid dual-stack IPv6/IPv4 implementation (e.g. Linux),
the code above will automatically listen on both IPv4 and IPv6 by using the
same TCP socket.
Can pyftpdlib be integrated with "real" users existing on the system?
---------------------------------------------------------------------
Yes. See `UnixAuthorizer`_ and `WindowsAuthorizer`_ classes. By using them you
can authenticate to the FTP server by using the credentials of the users
defined on the operating system
Furthermore: every time the FTP server accesses 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. It will do so by setting the
effective user or group ID of the current process. That means that you probably
want to run the FTP as root. See:
* https://github.com/giampaolo/pyftpdlib/blob/master/demo/unix_ftpd.py
* https://github.com/giampaolo/pyftpdlib/blob/master/demo/win_ftpd.py
Does pyftpdlib support FTP over TLS/SSL (FTPS)?
-----------------------------------------------
Yes. Checkout `TLS_FTPHandler`_.
What about SITE commands?
-------------------------
The only supported SITE command is *SITE CHMOD* (change file mode). The user
willing to add support for other specific SITE commands has to define a new
``ftp_SITE_CMD`` method in the `FTPHandler`_ subclass and add a new entry in
``proto_cmds`` dictionary. Example:
.. code-block:: python
from pyftpdlib.handlers import FTPHandler
proto_cmds = FTPHandler.proto_cmds.copy()
proto_cmds.update(
{'SITE RMTREE': dict(perm='R', auth=True, arg=True,
help='Syntax: SITE <SP> RMTREE <SP> path (remove directory tree).')}
)
class CustomizedFTPHandler(FTPHandler):
proto_cmds = proto_cmds
def ftp_SITE_RMTREE(self, line):
"""Recursively remove a directory tree."""
# implementation here
# ...
.. _`change the default concurrency model`: tutorial.html#changing-the-concurrency-model
.. _`demo/throttled_ftpd.py`: https://github.com/giampaolo/pyftpdlib/blob/master/demo/throttled_ftpd.py
.. _`FTPHandler.masquerade_address`: api.html#pyftpdlib.handlers.FTPHandler.masquerade_address
.. _`FTPHandler.passive_ports`: api.html#pyftpdlib.handlers.FTPHandler.passive_ports
.. _`FTPHandler.use_gmt_times`: api.html#pyftpdlib.handlers.FTPHandler.use_gmt_times
.. _`FTPHandler.use_sendfile`: api.html#pyftpdlib.handlers.FTPHandler.use_sendfile
.. _`FTPHandler`: api.html#pyftpdlib.handlers.FTPHandler
.. _`FTPServer.max_cons_per_ip`: api.html#pyftpdlib.servers.FTPServer.max_cons_per_ip
.. _`FTPServer.max_cons`: api.html#pyftpdlib.servers.FTPServer.max_cons
.. _`FTPServer`: api.html#pyftpdlib.servers.FTPServer
.. _`GMT times`: https://en.wikipedia.org/wiki/Greenwich_Mean_Time
.. _`install instructions`: install.html
.. _`Python`: https://www.python.org/
.. _`RFC-2428`: https://datatracker.ietf.org/doc/html/rfc2428
.. _`RFC-3659`: https://datatracker.ietf.org/doc/html/rfc3659
.. _`RFC-959`: https://datatracker.ietf.org/doc/html/rfc959
.. _`ThrottledDTPHandler.read_limit`: api.html#pyftpdlib.handlers.ThrottledDTPHandler.read_limit
.. _`ThrottledDTPHandler.write_limit`: api.html#pyftpdlib.handlers.ThrottledDTPHandler.write_limit
.. _`ThrottledDTPHandler`: api.html#pyftpdlib.handlers.ThrottledDTPHandler
.. _`TLS_FTPHandler`: api.html#pyftpdlib.handlers.TLS_FTPHandler
.. _`UnixAuthorizer`: api.html#pyftpdlib.authorizers.UnixAuthorizer
.. _`WindowsAuthorizer`: api.html#pyftpdlib.authorizers.WindowsAuthorizer
|