Codebase list policyd-rate-limit / dba2a81
New upstream version 1.0.0 Pierre-Elliott Bécue 5 years ago
12 changed file(s) with 181 addition(s) and 43 deletion(s). Raw diff Collapse all Expand all
88 [ ! -f /etc/policyd-rate-limit.yaml ] && cp -n policyd_rate_limit/policyd-rate-limit.yaml /etc/ || true
99 cp -n init/policyd-rate-limit /etc/init.d
1010 cp -n init/policyd-rate-limit.service /etc/systemd/system/ || true
11 pip3 install policyd-rate-limit -U --force-reinstall --no-deps --no-binary :all -f ./dist/policyd-rate-limit-${VERSION}.tar.gz
11 pip3 install policyd-rate-limit --no-cache-dir -U --force-reinstall --no-deps --no-binary :all -f ./dist/policyd-rate-limit-${VERSION}.tar.gz
1212 systemctl daemon-reload
1313 uninstall:
1414 pip3 uninstall policyd-rate-limit || true
00 Metadata-Version: 1.1
11 Name: policyd-rate-limit
2 Version: 0.7.1
2 Version: 1.0.0
33 Summary: Postfix rate limit policy server implemented in Python3.
44 Home-page: https://github.com/nitmir/policyd-rate-limit
55 Author: Valentin Samir
5757
5858 0 0 * * * policyd-rate-limit /usr/local/bin/policyd-rate-limit --clean >/dev/null
5959
60
61 Options of the ``policyd-rate-limit`` binary
62 --------------------------------------------
63
64 * ``-h``, ``--help``: show the help message and exit
65 * ``--clean``: clean old records from the database
66 * ``--get-config PARAMETER_NAME`` return the value of a config parameter
67 * ``--file CONFIG_PATH``, ``-f CONFIG_PATH``: path to a config file
68
6069 Settings
6170 --------
6271
63 ``policyd-rate-limit`` search for its config first in ``~/.config/policyd-rate-limit.conf``
64 If not found, then in ``/etc/policyd-rate-limit.conf``, and if not found use the default config.
72 If the option ``--file`` is not specified, ``policyd-rate-limit`` try to read its configuration from
73 the following path and choose the first existing file:
74
75 * ~/.config/policyd-rate-limit.conf
76 * ~/.config/policyd-rate-limit.yaml
77 * /etc/policyd-rate-limit.conf
78 * /etc/policyd-rate-limit.yaml
79
80 The ``.conf`` are the old configuration format. It was a python module and should not be used.
81 The ``.yaml`` are the new configuration format using the YAML syntax.
82
6583
6684 * ``debug``: make ``policyd-rate-limit`` output logs to stderr.
6785 The default is ``True``.
85103 * ``limits``: A list of couple [number of emails, number of seconds]. If one of the element of the
86104 list is exeeded (more than 'number of emails' on 'number of seconds' for an ip address or an sasl
87105 username), postfix will return a temporary failure.
88 * ``limits_by_id``: A dictionnary of id -> limit list (see limits). Used to override limits and use
106 * ``limits_by_id``: A dictionary of id -> limit list (see limits). Used to override limits and use
89107 custom limits for a particular id. Use an empty list for no limits for a particular id.
90108 Ids are sasl usernames or ip addresses. The default is ``{}``.
91109 * ``limit_by_sasl``: Apply limits by sasl usernames. The default is ``True``.
92 * ``limit_by_ip``: Apply limits by ip addresses if sasl username is not found.
110 * ``limit_by_sender``: Apply limits by sender addresses if sasl username is not found.
111 The defaut is ``False``.
112 * ``limit_by_ip``: Apply limits by ip addresses if sasl username and sender address are not found.
93113 The default is ``False``.
94114 * ``limited_networks``: A list of ip networks in cidr notation on which limits are applied. An empty
95115 list is equal to ``limit_by_ip = False``, put ``"0.0.0.0/0"`` and ``::/0`` for every ip addresses.
110130 --clean is called. The default is ``False``.
111131 * ``report_from``: From who to send emails reports. It must be defined when ``report`` is ``True``.
112132 * ``report_to``: Address to send emails reports to. It must be defined when ``report`` is ``True``.
133 It can be a single email address or a list of email adresses.
113134 * ``report_subject``: Subject of the report email. The default is ``"policyd-rate-limit report"``.
114135 * ``report_limits``: List of number of seconds from the limits list for which you want to be reported.
115136 The default is ``[86400]``.
154175 .. |travis| image:: https://badges.genua.fr/travis/nitmir/policyd-rate-limit/master.svg
155176 :target: https://travis-ci.org/nitmir/policyd-rate-limit
156177
157 .. |coverage| image:: https://badges.genua.fr/local/coverage/?project=policyd-rate-limit
158 :target: https://badges.genua.fr/local/coverage/policyd-rate-limit/
178 .. |coverage| image:: https://badges.genua.fr/coverage/badge/policyd-rate-limit/master.svg
179 :target: https://badges.genua.fr/coverage/policyd-rate-limit/
159180
160181 .. |pypi_version| image:: https://badges.genua.fr/pypi/v/policyd-rate-limit.svg
161182 :target: https://pypi.python.org/pypi/policyd-rate-limit
4848
4949 0 0 * * * policyd-rate-limit /usr/local/bin/policyd-rate-limit --clean >/dev/null
5050
51
52 Options of the ``policyd-rate-limit`` binary
53 --------------------------------------------
54
55 * ``-h``, ``--help``: show the help message and exit
56 * ``--clean``: clean old records from the database
57 * ``--get-config PARAMETER_NAME`` return the value of a config parameter
58 * ``--file CONFIG_PATH``, ``-f CONFIG_PATH``: path to a config file
59
5160 Settings
5261 --------
5362
54 ``policyd-rate-limit`` search for its config first in ``~/.config/policyd-rate-limit.conf``
55 If not found, then in ``/etc/policyd-rate-limit.conf``, and if not found use the default config.
63 If the option ``--file`` is not specified, ``policyd-rate-limit`` try to read its configuration from
64 the following path and choose the first existing file:
65
66 * ~/.config/policyd-rate-limit.conf
67 * ~/.config/policyd-rate-limit.yaml
68 * /etc/policyd-rate-limit.conf
69 * /etc/policyd-rate-limit.yaml
70
71 The ``.conf`` are the old configuration format. It was a python module and should not be used.
72 The ``.yaml`` are the new configuration format using the YAML syntax.
73
5674
5775 * ``debug``: make ``policyd-rate-limit`` output logs to stderr.
5876 The default is ``True``.
7694 * ``limits``: A list of couple [number of emails, number of seconds]. If one of the element of the
7795 list is exeeded (more than 'number of emails' on 'number of seconds' for an ip address or an sasl
7896 username), postfix will return a temporary failure.
79 * ``limits_by_id``: A dictionnary of id -> limit list (see limits). Used to override limits and use
97 * ``limits_by_id``: A dictionary of id -> limit list (see limits). Used to override limits and use
8098 custom limits for a particular id. Use an empty list for no limits for a particular id.
8199 Ids are sasl usernames or ip addresses. The default is ``{}``.
82100 * ``limit_by_sasl``: Apply limits by sasl usernames. The default is ``True``.
83 * ``limit_by_ip``: Apply limits by ip addresses if sasl username is not found.
101 * ``limit_by_sender``: Apply limits by sender addresses if sasl username is not found.
102 The defaut is ``False``.
103 * ``limit_by_ip``: Apply limits by ip addresses if sasl username and sender address are not found.
84104 The default is ``False``.
85105 * ``limited_networks``: A list of ip networks in cidr notation on which limits are applied. An empty
86106 list is equal to ``limit_by_ip = False``, put ``"0.0.0.0/0"`` and ``::/0`` for every ip addresses.
101121 --clean is called. The default is ``False``.
102122 * ``report_from``: From who to send emails reports. It must be defined when ``report`` is ``True``.
103123 * ``report_to``: Address to send emails reports to. It must be defined when ``report`` is ``True``.
124 It can be a single email address or a list of email adresses.
104125 * ``report_subject``: Subject of the report email. The default is ``"policyd-rate-limit report"``.
105126 * ``report_limits``: List of number of seconds from the limits list for which you want to be reported.
106127 The default is ``[86400]``.
145166 .. |travis| image:: https://badges.genua.fr/travis/nitmir/policyd-rate-limit/master.svg
146167 :target: https://travis-ci.org/nitmir/policyd-rate-limit
147168
148 .. |coverage| image:: https://badges.genua.fr/local/coverage/?project=policyd-rate-limit
149 :target: https://badges.genua.fr/local/coverage/policyd-rate-limit/
169 .. |coverage| image:: https://badges.genua.fr/coverage/badge/policyd-rate-limit/master.svg
170 :target: https://badges.genua.fr/coverage/policyd-rate-limit/
150171
151172 .. |pypi_version| image:: https://badges.genua.fr/pypi/v/policyd-rate-limit.svg
152173 :target: https://pypi.python.org/pypi/policyd-rate-limit
6767 exceeded (more than 'number of emails' on 'number of seconds' for an ip address or an sasl
6868 username), postfix will return a temporary failure.
6969 **limits_by_id**
70 A dictionnary of id -> limit list (see limits). Used to override limits and use custom limits for
70 A dictionary of id -> limit list (see limits). Used to override limits and use custom limits for
7171 a particular id. Use an empty list for no limits for a particular id. Ids are sasl usernames or
7272 ip addresses. The default is {}.
7373 **limit_by_sasl**
7474 Apply limits by sasl usernames. The default is True.
75 **limit_by_sender**
76 Apply limits by sender addresses if sasl username is not found. The defaut is ``False``.
7577 **limit_by_ip**
7678 Apply limits by ip addresses if sasl username is not found. The default is False.
7779 **limited_networks**
100102 From who to send emails reports. It must be defined when **report** is True.
101103 **report_to**
102104 Address to send emails reports to. It must be defined when **report** is True.
105 It can be a single email address or a list of email adresses.
103106 **report_subject**
104107 Subject of the report email. The default is "policyd-rate-limit report".
105108 **report_limits**
5252 limits_by_id = {}
5353
5454 limit_by_sasl = True
55 limit_by_sender = False
5556 limit_by_ip = False
5657
5758 limited_networks = []
6768 report = False
6869 # from who to send emails reports
6970 report_from = None
70 # address to send emails reports to
71 # address to send emails reports to. It can be a single email or a list of emails
7172 report_to = None
7273 # subject of the report email
7374 report_subject = "policyd-rate-limit report"
8283 smtp_starttls = False
8384 # Should we use credentials to connect to smtp_server ? if yes set ("user", "password"), else None
8485 smtp_credentials = None
86
87 # The time in seconds before an unused socket gets closed
88 delay_to_close = 300
5757 report = False
5858 # from who to send emails reports
5959 report_from = None
60 # address to send emails reports to
60 # address to send emails reports to. It can be a single email or a list of emails
6161 report_to = None
6262 # subject of the report email
6363 report_subject = "policyd-rate-limit report"
5555
5656 # Apply limits by sasl usernames.
5757 limit_by_sasl: True
58 # If no sasl username is found, apply limits by ip addresses.
58 # If no sasl username is found, or limit by sasl usernames disabled,
59 # apply limits by sender addresses.
60 limit_by_sender: False
61 # If sasl username and sender address not found or disabled, apply limits by ip addresses.
5962 limit_by_ip: False
6063
6164 # A list of ip networks in cidr notation on which limits are applied. An empty list is equal
7780 # from who to send emails reports. Must be defined if report: True
7881 report_from: null
7982 # Address to send emails reports to. Must be defined if report: True
83 # If can be a single email address:
84 # report_to: foo@example.com
85 # or a list of email addresses:
86 # report_to:
87 # - foo@example.com
88 # - bar@example.com
8089 report_to: null
8190 # Subject of the report email
8291 report_subject: "policyd-rate-limit report"
92101 smtp_starttls: False
93102 # Should we use credentials to connect to smtp_server ? if yes set ["user", "password"], else null
94103 smtp_credentials: null
104
105 # The time in seconds before an unused socket gets closed
106 delay_to_close: 300
1212 import socket
1313 import time
1414 import select
15 import traceback
1516
1617 from policyd_rate_limit import utils
1718 from policyd_rate_limit.utils import config
2526 """The policy server class"""
2627 socket_data_read = {}
2728 socket_data_write = {}
29 last_used = {}
2830
2931 def socket(self):
3032 """initialize the socket from the config parameters"""
6971 except KeyError:
7072 pass
7173 connection.close()
74
75 def close_write_conn(self, connection):
76 """Removes a socket from the write dict"""
77 try:
78 del self.socket_data_write[connection]
79 except KeyError:
80 if config.debug:
81 sys.stderr.write(
82 (
83 "Hmmm, a socket actually used to write a little "
84 "time ago wasn\'t in socket_data_write. Weird.\n"
85 )
86 )
7287
7388 def run(self):
7489 """The main server loop"""
95110 sys.stderr.write('connection from %s\n' % (client_address,))
96111 sys.stderr.flush()
97112 self.socket_data_read[connection] = []
113
114 # Updates the last_sed time for the socket.
115 self.last_used[connection] = time.time()
98116 # else there is data to read on a client socket
99117 else:
100118 self.read(socket)
106124 if data_not_sent:
107125 self.socket_data_write[socket] = data_not_sent
108126 else:
109 self.close_connection(socket)
127 self.close_write_conn(socket)
128
129 # Socket has been used, let's update its last_used time.
130 self.last_used[socket] = time.time()
110131 # the socket has been closed during read
111132 except KeyError:
112133 pass
134 # Closes unused socket for a long time.
135 __to_rm = []
136 for (socket, last_used) in self.last_used.items():
137 if socket == sock:
138 continue
139 if time.time() - last_used > config.delay_to_close:
140 self.close_connection(socket)
141 __to_rm.append(socket)
142 for socket in __to_rm:
143 self.last_used.pop(socket)
144
113145 except (KeyboardInterrupt, utils.Exit):
114146 for socket in list(self.socket_data_read.keys()):
115147 if socket != self.sock:
153185 self.action(connection, request)
154186 else:
155187 self.socket_data_read[connection] = buffer
188 # Socket has been used, let's update its last_used time.
189 self.last_used[connection] = time.time()
156190 except (KeyboardInterrupt, utils.Exit):
157191 self.close_connection(connection)
158192 raise
159193 except Exception as error:
160 sys.stderr.write("%s\n" % error)
194 traceback.print_exc()
161195 sys.stderr.flush()
162196 self.close_connection(connection)
163197
179213 # if user is authenticated, we filter by sasl username
180214 if config.limit_by_sasl and u'sasl_username' in request:
181215 id = request[u'sasl_username']
216 # else, if activated, we filter by sender
217 elif config.limit_by_sender and u'sender' in request:
218 id = request[u'sender']
182219 # else, if activated, we filter by ip source addresse
183220 elif (
184221 config.limit_by_ip and
190227 # to the next section
191228 else:
192229 raise Pass()
193 # Here we are limiting agains sasl username or ip source addresses.
230 # Here we are limiting against sasl username, sender or source ip addresses.
194231 # for each limit periods, we count the number of mails already send.
195232 # if the a limit is reach, we change action to fail (deny the mail).
196233 for mail_nb, delta in config.limits_by_id.get(id, config.limits):
231268 sys.stderr.flush()
232269 # return the result to the client
233270 self.socket_data_write[connection] = data.encode('UTF-8')
271 # Socket has been used, let's update its last_used time.
272 self.last_used[connection] = time.time()
144144 p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
145145 launch_instance.i += 1
146146 return (p, cfg_path)
147
148
147149 launch_instance.i = 0
148150
149151
267267 def del_db(cls):
268268 try:
269269 cls._db[threading.current_thread()].close()
270 except:
270 except Exception as e:
271271 pass
272272 try:
273273 del cls._db[threading.current_thread()]
331331 print("%d records deleted" % cur.rowcount)
332332 # if report is True, generate a mail report
333333 if config.report and config.report_to:
334 send_report(cur)
335 # The mail report has been successfully send, flush limit_report
336 cur.execute("DELETE FROM limit_report")
334 report_text = gen_report(cur)
335 # The mail report has been successfully send, flush limit_report
336 cur.execute("DELETE FROM limit_report")
337 # send report
338 if len(report_text) != 0:
339 send_report(report_text)
337340
338341 try:
339342 if config.backend == PGSQL_DB:
354357 cursor.get_db().autocommit = False
355358
356359
357 def send_report(cur):
360 def gen_report(cur):
358361 cur.execute("SELECT id, delta, hit FROM limit_report")
359362 # list to sort ids by hits
360363 report = list(cur.fetchall())
364 text = []
361365 if not config.report_only_if_needed or report:
362366 if report:
363367 text = ["Below is the table of users who hit a limit since the last cleanup:", ""]
367371 for (id, delta, hit) in report:
368372 report_d[id].append((delta, hit))
369373 max_d['id'] = max(max_d['id'], len(id))
370 max_d['delta'] = max(max_d['delta'], len(str(delta)))
374 max_d['delta'] = max(max_d['delta'], len(str(delta)) + 1)
371375 max_d['hit'] = max(max_d['hit'], len(str(hit)))
372376 # sort by hits
373377 report.sort(key=lambda x: x[2])
403407 else:
404408 text = ["No user hit a limit since the last cleanup"]
405409 text.extend(["", "-- ", "policyd-rate-limit"])
406
407 # Start building the mail report
408 msg = MIMEMultipart()
409 msg['Subject'] = config.report_subject or ""
410 msg['From'] = config.report_from or ""
411 msg['To'] = config.report_to
412 msg.attach(MIMEText("\n".join(text), 'plain'))
410 return text
411
412
413 def send_report(text):
413414
414415 # check that smtp_server is wekk formated
415416 if isinstance(config.smtp_server, (list, tuple)):
435436 server.login(config.smtp_credentials[0], config.smtp_credentials[1])
436437 else:
437438 ValueError("bad smtp_credentials should be a tuple (login, password)")
438 server.sendmail(config.report_from or "", config.report_to, msg.as_string())
439
440 if not isinstance(config.report_to, list):
441 report_to = [config.report_to]
442 else:
443 report_to = config.report_to
444 for rcpt in report_to:
445 # Start building the mail report
446 msg = MIMEMultipart()
447 msg['Subject'] = config.report_subject or ""
448 msg['From'] = config.report_from or ""
449 msg['To'] = rcpt
450 msg.attach(MIMEText("\n".join(text), 'plain'))
451 server.sendmail(config.report_from or "", rcpt, msg.as_string())
439452 finally:
453 print('report is sent')
440454 server.quit()
441455
442456
537551 Used for coverage computation"""
538552 raise Exit()
539553
554
540555 config = LazyConfig()
00 Metadata-Version: 1.1
11 Name: policyd-rate-limit
2 Version: 0.7.1
2 Version: 1.0.0
33 Summary: Postfix rate limit policy server implemented in Python3.
44 Home-page: https://github.com/nitmir/policyd-rate-limit
55 Author: Valentin Samir
5757
5858 0 0 * * * policyd-rate-limit /usr/local/bin/policyd-rate-limit --clean >/dev/null
5959
60
61 Options of the ``policyd-rate-limit`` binary
62 --------------------------------------------
63
64 * ``-h``, ``--help``: show the help message and exit
65 * ``--clean``: clean old records from the database
66 * ``--get-config PARAMETER_NAME`` return the value of a config parameter
67 * ``--file CONFIG_PATH``, ``-f CONFIG_PATH``: path to a config file
68
6069 Settings
6170 --------
6271
63 ``policyd-rate-limit`` search for its config first in ``~/.config/policyd-rate-limit.conf``
64 If not found, then in ``/etc/policyd-rate-limit.conf``, and if not found use the default config.
72 If the option ``--file`` is not specified, ``policyd-rate-limit`` try to read its configuration from
73 the following path and choose the first existing file:
74
75 * ~/.config/policyd-rate-limit.conf
76 * ~/.config/policyd-rate-limit.yaml
77 * /etc/policyd-rate-limit.conf
78 * /etc/policyd-rate-limit.yaml
79
80 The ``.conf`` are the old configuration format. It was a python module and should not be used.
81 The ``.yaml`` are the new configuration format using the YAML syntax.
82
6583
6684 * ``debug``: make ``policyd-rate-limit`` output logs to stderr.
6785 The default is ``True``.
85103 * ``limits``: A list of couple [number of emails, number of seconds]. If one of the element of the
86104 list is exeeded (more than 'number of emails' on 'number of seconds' for an ip address or an sasl
87105 username), postfix will return a temporary failure.
88 * ``limits_by_id``: A dictionnary of id -> limit list (see limits). Used to override limits and use
106 * ``limits_by_id``: A dictionary of id -> limit list (see limits). Used to override limits and use
89107 custom limits for a particular id. Use an empty list for no limits for a particular id.
90108 Ids are sasl usernames or ip addresses. The default is ``{}``.
91109 * ``limit_by_sasl``: Apply limits by sasl usernames. The default is ``True``.
92 * ``limit_by_ip``: Apply limits by ip addresses if sasl username is not found.
110 * ``limit_by_sender``: Apply limits by sender addresses if sasl username is not found.
111 The defaut is ``False``.
112 * ``limit_by_ip``: Apply limits by ip addresses if sasl username and sender address are not found.
93113 The default is ``False``.
94114 * ``limited_networks``: A list of ip networks in cidr notation on which limits are applied. An empty
95115 list is equal to ``limit_by_ip = False``, put ``"0.0.0.0/0"`` and ``::/0`` for every ip addresses.
110130 --clean is called. The default is ``False``.
111131 * ``report_from``: From who to send emails reports. It must be defined when ``report`` is ``True``.
112132 * ``report_to``: Address to send emails reports to. It must be defined when ``report`` is ``True``.
133 It can be a single email address or a list of email adresses.
113134 * ``report_subject``: Subject of the report email. The default is ``"policyd-rate-limit report"``.
114135 * ``report_limits``: List of number of seconds from the limits list for which you want to be reported.
115136 The default is ``[86400]``.
154175 .. |travis| image:: https://badges.genua.fr/travis/nitmir/policyd-rate-limit/master.svg
155176 :target: https://travis-ci.org/nitmir/policyd-rate-limit
156177
157 .. |coverage| image:: https://badges.genua.fr/local/coverage/?project=policyd-rate-limit
158 :target: https://badges.genua.fr/local/coverage/policyd-rate-limit/
178 .. |coverage| image:: https://badges.genua.fr/coverage/badge/policyd-rate-limit/master.svg
179 :target: https://badges.genua.fr/coverage/policyd-rate-limit/
159180
160181 .. |pypi_version| image:: https://badges.genua.fr/pypi/v/policyd-rate-limit.svg
161182 :target: https://pypi.python.org/pypi/policyd-rate-limit
3636
3737 setup(
3838 name='policyd-rate-limit',
39 version='0.7.1',
39 version='1.0.0',
4040 description=DESC,
4141 long_description=README,
4242 author='Valentin Samir',