Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions demo/proxy_protocol_untrusted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python

# Copyright (C) 2007 Giampaolo Rodola' <g.rodola@gmail.com>.
# Use of this source code is governed by MIT license that can be
# found in the LICENSE file.

"""A basic FTP server which uses a DummyAuthorizer for managing 'virtual
users', setting a limit for incoming connections and a range of passive
ports. Accepts connections from proxies implementing the PROXY protocol.
"""

import os

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer


def main():
# Instantiate a dummy authorizer for managing 'virtual' users
authorizer = DummyAuthorizer()

# Define a new user having full r/w permissions and a read-only
# anonymous user
authorizer.add_user('user', '12345', os.getcwd(), perm='elradfmwMT')
authorizer.add_anonymous(os.getcwd())

# Instantiate FTP handler class
handler = FTPHandler
handler.authorizer = authorizer

# Define a customized banner (string returned when client connects)
handler.banner = "pyftpdlib based ftpd ready."

# Specify a masquerade address and the range of ports to use for
# passive connections. Decomment in case you're behind a NAT.
# handler.masquerade_address = '151.25.42.11'
handler.passive_ports = range(20000, 22535)


# Instantiate FTP server class and listen on 0.0.0.0:2121
address = ('', 2121)
FTPServer.proxy_proto_enabled = True
FTPServer.proxy_proto_allow_untrusted = True
server = 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()
16 changes: 16 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,22 @@ Server (acceptor)
Number of maximum connections accepted for the same IP address (default
``0`` == no limit).

.. data:: proxy_proto_enabled

Whether to enable the PROXY protocol for incoming connections (default ``False``)

.. data:: proxy_proto_trusted_nets

Contains a list of trusted proxies (written as strings) allowed to communicate
with the server. Use a /32 (or /128) network mask if you want to declare a
single IP, for example ``['10.11.12.13/32', 'fc88::2/128']`` (defaults to ``[]``).

.. data:: proxy_proto_allow_untrusted

Whether or not to parse non trusted (as defined by :data:`proxy_proto_trusted_nets`)
proxies headers (defaults to ``False``).


.. method:: serve_forever(timeout=None, blocking=True, handle_exit=True, worker_processes=1)

Starts the asynchronous IO loop.
Expand Down
10 changes: 10 additions & 0 deletions pyftpdlib/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,7 @@ def __init__(self, conn, server, ioloop=None):
self.remote_ip = ""
self.remote_port = ""
self.started = time.time()
self.proxy_proto_obj = self.server.proxy_proto_obj

# private session attributes
self._last_response = ""
Expand Down Expand Up @@ -1294,6 +1295,15 @@ def __init__(self, conn, server, ioloop=None):
self.handle_error()
return
else:
if self.server.proxy_proto_enabled and self.proxy_proto_obj and \
(self.proxy_proto_obj.trusted or self.server.proxy_proto_allow_untrusted):
# Override the socket values if PROXY protocol data is
# available and comes from a trusted proxy
if self.proxy_proto_obj.remote_ip:
self.remote_ip = self.proxy_proto_obj.remote_ip
if self.proxy_proto_obj.remote_port:
self.remote_port = self.proxy_proto_obj.remote_port

self.log("FTP session opened (connect)")

# try to handle urgent data inline
Expand Down
38 changes: 38 additions & 0 deletions pyftpdlib/ioloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def handle_accepted(self, sock, addr):
from .log import debug
from .log import is_logging_configured
from .log import logger
from .proxy_proto import ProxyProtocol


timer = getattr(time, 'monotonic', time.time)
Expand Down Expand Up @@ -977,8 +978,30 @@ def add_channel(self, map=None, events=None):
class Acceptor(AsyncChat):
"""Same as base AsyncChat and supposed to be used to
accept new connections.

All relevant PROXY protocol information is stored in class attributes
described below.

- (bool) proxy_proto_enabled:
enable the use of PROXY protocol (defaults to False).

- (list) proxy_proto_trusted_nets:
the IP networks (written as strings) of the proxies you want to
trust. Use a /32 (or /128) network mask if you want to declare a
single IP (defaults to []).

- (bool) proxy_proto_allow_untrusted:
whether or not to parse untrusted proxies headers (defaults to False).

- (instance) proxy_proto_obj:
the ProxyProtocol instance populated with header's information
"""

proxy_proto_enabled = False
proxy_proto_trusted_nets = []
proxy_proto_allow_untrusted = False
proxy_proto_obj = None

def add_channel(self, map=None, events=None):
AsyncChat.add_channel(self, map=map, events=self.ioloop.READ)

Expand Down Expand Up @@ -1045,6 +1068,21 @@ def handle_accept(self):
debug("call: handle_accept(); accept() returned ECONNABORTED",
self)
else:
if self.proxy_proto_enabled:
# Retrieve a populated PROXY protocol object. If no exception
# is raised the returned object is considered valid
try:
ProxyProtocol.trusted_networks = self.proxy_proto_trusted_nets
ProxyProtocol.allow_untrusted = self.proxy_proto_allow_untrusted
self.proxy_proto_obj = ProxyProtocol.create(sock)
except Exception as e:
logger.error("proxy: {}".format(e))
return

if self.proxy_proto_obj.trusted or self.proxy_proto_allow_untrusted:
# Could result in a (None, None) tuple
addr = (self.proxy_proto_obj.remote_ip, self.proxy_proto_obj.remote_port)

# sometimes addr == None instead of (ip, port) (see issue 104)
if addr is not None:
self.handle_accepted(sock, addr)
Expand Down
Loading