1#!/usr/local/bin/python3.8
2
3# Copyright (C) 2007 Giampaolo Rodola' <g.rodola@gmail.com>.
4# Use of this source code is governed by MIT license that can be
5# found in the LICENSE file.
6
7"""
8A FTP server banning clients in case of commands flood.
9
10If client sends more than 300 requests per-second it will be
11disconnected and won't be able to re-connect for 1 hour.
12"""
13
14from pyftpdlib.authorizers import DummyAuthorizer
15from pyftpdlib.handlers import FTPHandler
16from pyftpdlib.servers import FTPServer
17
18
19class AntiFloodHandler(FTPHandler):
20
21    cmds_per_second = 300  # max number of cmds per second
22    ban_for = 60 * 60      # 1 hour
23    banned_ips = []
24
25    def __init__(self, *args, **kwargs):
26        FTPHandler.__init__(self, *args, **kwargs)
27        self.processed_cmds = 0
28        self.pcmds_callback = \
29            self.ioloop.call_every(1, self.check_processed_cmds)
30
31    def on_connect(self):
32        # called when client connects.
33        if self.remote_ip in self.banned_ips:
34            self.respond('550 You are banned.')
35            self.close_when_done()
36
37    def check_processed_cmds(self):
38        # called every second; checks for the number of commands
39        # sent in the last second.
40        if self.processed_cmds > self.cmds_per_second:
41            self.ban(self.remote_ip)
42        else:
43            self.processed_cmds = 0
44
45    def process_command(self, *args, **kwargs):
46        # increase counter for every received command
47        self.processed_cmds += 1
48        FTPHandler.process_command(self, *args, **kwargs)
49
50    def ban(self, ip):
51        # ban ip and schedule next un-ban
52        if ip not in self.banned_ips:
53            self.log('banned IP %s for command flooding' % ip)
54        self.respond('550 You are banned for %s seconds.' % self.ban_for)
55        self.close()
56        self.banned_ips.append(ip)
57
58    def unban(self, ip):
59        # unban ip
60        try:
61            self.banned_ips.remove(ip)
62        except ValueError:
63            pass
64        else:
65            self.log('unbanning IP %s' % ip)
66
67    def close(self):
68        FTPHandler.close(self)
69        if not self.pcmds_callback.cancelled:
70            self.pcmds_callback.cancel()
71
72
73def main():
74    authorizer = DummyAuthorizer()
75    authorizer.add_user('user', '12345', '.', perm='elradfmwMT')
76    authorizer.add_anonymous('.')
77    handler = AntiFloodHandler
78    handler.authorizer = authorizer
79    server = FTPServer(('', 2121), handler)
80    server.serve_forever(timeout=1)
81
82
83if __name__ == '__main__':
84    main()
85