Merge branch 'master' of https://github.com/Mailu/Mailu into wildcard_senders

master
Florent Daigniere 3 years ago
commit 9ec7590171

@ -47,6 +47,7 @@ DEFAULT_CONFIG = {
'DKIM_SELECTOR': 'dkim', 'DKIM_SELECTOR': 'dkim',
'DKIM_PATH': '/dkim/{domain}.{selector}.key', 'DKIM_PATH': '/dkim/{domain}.{selector}.key',
'DEFAULT_QUOTA': 1000000000, 'DEFAULT_QUOTA': 1000000000,
'MESSAGE_RATELIMIT': '200/day',
# Web settings # Web settings
'SITENAME': 'Mailu', 'SITENAME': 'Mailu',
'WEBSITE': 'https://mailu.io', 'WEBSITE': 'https://mailu.io',

@ -81,6 +81,13 @@ def handle_authentication(headers):
raw_password = urllib.parse.unquote(headers["Auth-Pass"]) raw_password = urllib.parse.unquote(headers["Auth-Pass"])
password = raw_password.encode("iso8859-1").decode("utf8") password = raw_password.encode("iso8859-1").decode("utf8")
ip = urllib.parse.unquote(headers["Client-Ip"]) ip = urllib.parse.unquote(headers["Client-Ip"])
service_port = int(urllib.parse.unquote(headers["Auth-Port"]))
if service_port == 25:
return {
"Auth-Status": "AUTH not supported",
"Auth-Error-Code": "502 5.5.1",
"Auth-Wait": 0
}
user = models.User.query.get(user_email) user = models.User.query.get(user_email)
if check_credentials(user, password, ip, protocol): if check_credentials(user, password, ip, protocol):
return { return {

@ -1,5 +1,6 @@
from mailu import models from mailu import models, utils
from mailu.internal import internal from mailu.internal import internal
from flask import current_app as app
import flask import flask
import idna import idna
@ -31,7 +32,6 @@ def postfix_alias_map(alias):
destination = models.Email.resolve_destination(localpart, domain_name) destination = models.Email.resolve_destination(localpart, domain_name)
return flask.jsonify(",".join(destination)) if destination else flask.abort(404) return flask.jsonify(",".join(destination)) if destination else flask.abort(404)
@internal.route("/postfix/transport/<path:email>") @internal.route("/postfix/transport/<path:email>")
def postfix_transport(email): def postfix_transport(email):
if email == '*' or re.match("(^|.*@)\[.*\]$", email): if email == '*' or re.match("(^|.*@)\[.*\]$", email):
@ -141,6 +141,12 @@ def postfix_sender_login(sender):
destination = [*destination, *wildcard_senders] if destination else [*wildcard_senders] destination = [*destination, *wildcard_senders] if destination else [*wildcard_senders]
return flask.jsonify(",".join(destination)) if destination else flask.abort(404) return flask.jsonify(",".join(destination)) if destination else flask.abort(404)
@internal.route("/postfix/sender/rate/<path:sender>")
def postfix_sender_rate(sender):
""" Rate limit outbound emails per sender login
"""
user = models.User.get(sender) or flask.abort(404)
return flask.abort(404) if user.sender_limiter.hit() else flask.jsonify("450 4.2.1 You are sending too many emails too fast.")
@internal.route("/postfix/sender/access/<path:sender>") @internal.route("/postfix/sender/access/<path:sender>")
def postfix_sender_access(sender): def postfix_sender_access(sender):

@ -27,7 +27,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.inspection import inspect from sqlalchemy.inspection import inspect
from werkzeug.utils import cached_property from werkzeug.utils import cached_property
from mailu import dkim from mailu import dkim, utils
db = flask_sqlalchemy.SQLAlchemy() db = flask_sqlalchemy.SQLAlchemy()
@ -501,6 +501,12 @@ class User(Base, Email):
self.reply_enddate > now self.reply_enddate > now
) )
@property
def sender_limiter(self):
return utils.limiter.get_limiter(
app.config["MESSAGE_RATELIMIT"], "sender", self.email
)
@classmethod @classmethod
def get_password_context(cls): def get_password_context(cls):
""" create password context for hashing and verification """ create password context for hashing and verification

@ -19,7 +19,8 @@
<th>{% trans %}User settings{% endtrans %}</th> <th>{% trans %}User settings{% endtrans %}</th>
<th>{% trans %}Email{% endtrans %}</th> <th>{% trans %}Email{% endtrans %}</th>
<th>{% trans %}Features{% endtrans %}</th> <th>{% trans %}Features{% endtrans %}</th>
<th>{% trans %}Quota{% endtrans %}</th> <th>{% trans %}Storage Quota{% endtrans %}</th>
<th>{% trans %}Sending Quota{% endtrans %}</th>
<th>{% trans %}Comment{% endtrans %}</th> <th>{% trans %}Comment{% endtrans %}</th>
<th>{% trans %}Created{% endtrans %}</th> <th>{% trans %}Created{% endtrans %}</th>
<th>{% trans %}Last edit{% endtrans %}</th> <th>{% trans %}Last edit{% endtrans %}</th>
@ -41,6 +42,8 @@
{% if user.enable_pop %}<span class="label label-info">pop3</span>{% endif %} {% if user.enable_pop %}<span class="label label-info">pop3</span>{% endif %}
</td> </td>
<td>{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td> <td>{{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }}</td>
{% set limiter = user.sender_limiter %}
<td>{{ limiter.get_window_stats()[1] }} / {{ limiter.limit }}</td>
<td>{{ user.comment or '-' }}</td> <td>{{ user.comment or '-' }}</td>
<td>{{ user.created_at }}</td> <td>{{ user.created_at }}</td>
<td>{{ user.updated_at or '' }}</td> <td>{{ user.updated_at or '' }}</td>

@ -1,13 +1,11 @@
-----BEGIN DH PARAMETERS----- -----BEGIN DH PARAMETERS-----
MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz MIIBiAKCAYEAtQlUSOKGjpdXJ154qmMEa1pEs+9CdSxWiZFkiXBJb0lTafOh8cfF
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 2IkcWSwzxWwjW4Ad26UQQFh1poGf2QBzVk2vuKCekYzPAs/WqH8VwiXBiWR5R9lh
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 v/+CkEBYuQOzAhXLN6ZGdPPa2sjdI49rlaIqyLJE4D0TI/VHYmC/vEwqkJUgaGrS
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 19LhHZimnmouvrnyBPyf00czXlMow0RnmYeHVZ7W5hu7t9TH9o3QAN/GKiFfxFj+
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD RkdLM7beQdS0He5YeTaElM5l1YT5d5gHFbOzEQyKHd10ux+bgVcgUeVbBnI1SAIC
ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 w53yc1PkDAiRijSP5j5aWq1djtJPheS13o35HyIf0cHzkNYhKfX5JWPj/cbgdM+C
7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 FL1bnRc8sL5oxmkDoGJhiNZIf4n2WtS8Zu28gUgat6S+vCm/4yavIc/T1g6UiNKE
nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e X41HPbsma/QWUwOL6S+b2qr+7rKqjI5TzVek8vBMellEV4mBvfQU3NDSQ4WvxbTq
8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx ZEOgLPA178nrAgEC
iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
-----END DH PARAMETERS----- -----END DH PARAMETERS-----

@ -250,6 +250,7 @@ mail {
listen 10025; listen 10025;
protocol smtp; protocol smtp;
smtp_auth plain; smtp_auth plain;
auth_http_header Auth-Port 10025;
} }
# Default IMAP server for the webmail (no encryption, but authentication) # Default IMAP server for the webmail (no encryption, but authentication)
@ -257,6 +258,7 @@ mail {
listen 10143; listen 10143;
protocol imap; protocol imap;
smtp_auth plain; smtp_auth plain;
auth_http_header Auth-Port 10043;
} }
# SMTP is always enabled, to avoid losing emails when TLS is failing # SMTP is always enabled, to avoid losing emails when TLS is failing
@ -271,6 +273,7 @@ mail {
{% endif %} {% endif %}
protocol smtp; protocol smtp;
smtp_auth none; smtp_auth none;
auth_http_header Auth-Port 25;
} }
# All other protocols are disabled if TLS is failing # All other protocols are disabled if TLS is failing
@ -283,6 +286,7 @@ mail {
{% endif %} {% endif %}
protocol imap; protocol imap;
imap_auth plain; imap_auth plain;
auth_http_header Auth-Port 143;
} }
server { server {
@ -293,6 +297,7 @@ mail {
{% endif %} {% endif %}
protocol pop3; protocol pop3;
pop3_auth plain; pop3_auth plain;
auth_http_header Auth-Port 110;
} }
server { server {
@ -303,6 +308,7 @@ mail {
{% endif %} {% endif %}
protocol smtp; protocol smtp;
smtp_auth plain login; smtp_auth plain login;
auth_http_header Auth-Port 587;
} }
{% if TLS %} {% if TLS %}
@ -311,6 +317,7 @@ mail {
listen [::]:465 ssl; listen [::]:465 ssl;
protocol smtp; protocol smtp;
smtp_auth plain login; smtp_auth plain login;
auth_http_header Auth-Port 465;
} }
server { server {
@ -318,6 +325,7 @@ mail {
listen [::]:993 ssl; listen [::]:993 ssl;
protocol imap; protocol imap;
imap_auth plain; imap_auth plain;
auth_http_header Auth-Port 993;
} }
server { server {
@ -325,6 +333,7 @@ mail {
listen [::]:995 ssl; listen [::]:995 ssl;
protocol pop3; protocol pop3;
pop3_auth plain; pop3_auth plain;
auth_http_header Auth-Port 995;
} }
{% endif %} {% endif %}
{% endif %} {% endif %}

@ -1,5 +1,10 @@
ssl_certificate {{ TLS[0] }}; ssl_certificate {{ TLS[0] }};
ssl_certificate_key {{ TLS[1] }}; ssl_certificate_key {{ TLS[1] }};
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
ssl_certificate {{ TLS[2] }};
ssl_certificate_key {{ TLS[3] }};
ssl_trusted_certificate /etc/ssl/certs/ca-cert-DST_Root_CA_X3.pem;
{% endif %}
ssl_session_timeout 1d; ssl_session_timeout 1d;
ssl_session_tickets off; ssl_session_tickets off;
ssl_dhparam /conf/dhparam.pem; ssl_dhparam /conf/dhparam.pem;

@ -26,11 +26,11 @@ cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem")
keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem") keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem")
args["TLS"] = { args["TLS"] = {
"cert": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name), "cert": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name),
"letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem", "letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem",
"/certs/letsencrypt/live/mailu/privkey.pem"), "/certs/letsencrypt/live/mailu/privkey.pem", "/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem", "/certs/letsencrypt/live/mailu-ecdsa/privkey.pem"),
"mail": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name), "mail": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name),
"mail-letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem", "mail-letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem",
"/certs/letsencrypt/live/mailu/privkey.pem"), "/certs/letsencrypt/live/mailu/privkey.pem", "/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem", "/certs/letsencrypt/live/mailu-ecdsa/privkey.pem"),
"notls": None "notls": None
}[args["TLS_FLAVOR"]] }[args["TLS_FLAVOR"]]

@ -4,7 +4,6 @@ import os
import time import time
import subprocess import subprocess
command = [ command = [
"certbot", "certbot",
"-n", "--agree-tos", # non-interactive "-n", "--agree-tos", # non-interactive
@ -14,16 +13,45 @@ command = [
"--cert-name", "mailu", "--cert-name", "mailu",
"--preferred-challenges", "http", "--http-01-port", "8008", "--preferred-challenges", "http", "--http-01-port", "8008",
"--keep-until-expiring", "--keep-until-expiring",
"--rsa-key-size", "4096", "--config-dir", "/certs/letsencrypt",
"--post-hook", "/config.py"
]
command2 = [
"certbot",
"-n", "--agree-tos", # non-interactive
"-d", os.environ["HOSTNAMES"],
"-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
"certonly", "--standalone",
"--cert-name", "mailu-ecdsa",
"--preferred-challenges", "http", "--http-01-port", "8008",
"--keep-until-expiring",
"--key-type", "ecdsa",
"--config-dir", "/certs/letsencrypt", "--config-dir", "/certs/letsencrypt",
"--post-hook", "/config.py" "--post-hook", "/config.py"
] ]
def format_for_nginx(fullchain, output):
""" We may want to strip ISRG Root X1 out
"""
certs = []
with open(fullchain, 'r') as pem:
cert = ''
for line in pem:
cert += line
if '-----END CERTIFICATE-----' in line:
certs += [cert]
cert = ''
with open(output, 'w') as pem:
for cert in certs[:-1] if len(certs)>2 and os.getenv('LETSENCRYPT_SHORTCHAIN', default="False") else certs:
pem.write(cert)
# Wait for nginx to start # Wait for nginx to start
time.sleep(5) time.sleep(5)
# Run certbot every hour # Run certbot every day
while True: while True:
subprocess.call(command) subprocess.call(command)
time.sleep(3600) format_for_nginx('/certs/letsencrypt/live/mailu/fullchain.pem', '/certs/letsencrypt/live/mailu/nginx-chain.pem')
subprocess.call(command2)
format_for_nginx('/certs/letsencrypt/live/mailu-ecdsa/fullchain.pem', '/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem')
time.sleep(86400)

@ -33,7 +33,8 @@ relayhost = {{ RELAYHOST }}
{% if RELAYUSER %} {% if RELAYUSER %}
smtp_sasl_auth_enable = yes smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous smtp_sasl_security_options = noanonymous, noplaintext
smtp_sasl_tls_security_options = noanonymous
{% endif %} {% endif %}
# Recipient delimiter for extended addresses # Recipient delimiter for extended addresses
@ -50,7 +51,7 @@ smtpd_authorized_xclient_hosts={{ POD_ADDRESS_RANGE or SUBNET }}
# General TLS configuration # General TLS configuration
tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
tls_preempt_cipherlist = yes tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSION tls_ssl_options = NO_COMPRESSION, NO_TICKET
# By default, outgoing TLS is more flexible because # By default, outgoing TLS is more flexible because
# 1. not all receiving servers will support TLS, # 1. not all receiving servers will support TLS,
@ -58,7 +59,8 @@ tls_ssl_options = NO_COMPRESSION
smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('may') }} smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('may') }}
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
smtp_tls_protocols =!SSLv2,!SSLv3 smtp_tls_protocols =!SSLv2,!SSLv3
smtp_tls_session_cache_database = lmdb:${data_directory}/smtp_scache smtp_tls_session_cache_database = lmdb:/dev/shm/postfix/smtp_scache
smtpd_tls_session_cache_database = lmdb:/dev/shm/postfix/smtpd_scache
############### ###############
# Virtual # Virtual
@ -99,6 +101,8 @@ smtpd_sender_login_maps = ${podop}senderlogin
# Restrictions for incoming SMTP, other restrictions are applied in master.cf # Restrictions for incoming SMTP, other restrictions are applied in master.cf
smtpd_helo_required = yes smtpd_helo_required = yes
check_ratelimit = check_sasl_access ${podop}senderrate
smtpd_client_restrictions = smtpd_client_restrictions =
permit_mynetworks, permit_mynetworks,
check_sender_access ${podop}senderaccess, check_sender_access ${podop}senderaccess,

@ -7,7 +7,8 @@ smtp inet n - n - - smtpd
# Internal SMTP service # Internal SMTP service
10025 inet n - n - - smtpd 10025 inet n - n - - smtpd
-o smtpd_sasl_auth_enable=yes -o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=reject_unlisted_sender,reject_authenticated_sender_login_mismatch,permit -o smtpd_discard_ehlo_keywords=pipelining
-o smtpd_client_restrictions=$check_ratelimit,reject_unlisted_sender,reject_authenticated_sender_login_mismatch,permit
-o smtpd_reject_unlisted_recipient={% if REJECT_UNLISTED_RECIPIENT %}{{ REJECT_UNLISTED_RECIPIENT }}{% else %}no{% endif %} -o smtpd_reject_unlisted_recipient={% if REJECT_UNLISTED_RECIPIENT %}{{ REJECT_UNLISTED_RECIPIENT }}{% else %}no{% endif %}
-o cleanup_service_name=outclean -o cleanup_service_name=outclean
outclean unix n - n - 0 cleanup outclean unix n - n - 0 cleanup

@ -15,6 +15,7 @@ log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
def start_podop(): def start_podop():
os.setuid(getpwnam('postfix').pw_uid) os.setuid(getpwnam('postfix').pw_uid)
os.mkdir('/dev/shm/postfix',mode=0o700)
url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/" url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/"
# TODO: Remove verbosity setting from Podop? # TODO: Remove verbosity setting from Podop?
run_server(0, "postfix", "/tmp/podop.socket", [ run_server(0, "postfix", "/tmp/podop.socket", [
@ -25,7 +26,8 @@ def start_podop():
("recipientmap", "url", url + "recipient/map/§"), ("recipientmap", "url", url + "recipient/map/§"),
("sendermap", "url", url + "sender/map/§"), ("sendermap", "url", url + "sender/map/§"),
("senderaccess", "url", url + "sender/access/§"), ("senderaccess", "url", url + "sender/access/§"),
("senderlogin", "url", url + "sender/login/§") ("senderlogin", "url", url + "sender/login/§"),
("senderrate", "url", url + "sender/rate/§")
]) ])
def is_valid_postconf_line(line): def is_valid_postconf_line(line):

@ -165,6 +165,11 @@ See the `python docs`_ for more information.
.. _`python docs`: https://docs.python.org/3.6/library/logging.html#logging-levels .. _`python docs`: https://docs.python.org/3.6/library/logging.html#logging-levels
The ``LETSENCRYPT_SHORTCHAIN`` (default: False) setting controls whether we send the ISRG Root X1 certificate in TLS handshakes. This is required for `android handsets older than 7.1.1` but slows down the performance of modern devices.
.. _`android handsets older than 7.1.1`: https://community.letsencrypt.org/t/production-chain-changes/150739
Antivirus settings Antivirus settings
------------------ ------------------

@ -315,6 +315,21 @@ This page is also accessible for domain managers. On the users page new users ca
* Fetched accounts. Access the fetched accounts page of the user. See the :ref:`fetched accounts page <webadministration_fetched_accounts>` for more information. * Fetched accounts. Access the fetched accounts page of the user. See the :ref:`fetched accounts page <webadministration_fetched_accounts>` for more information.
This page also shows an overview of the following settings of an user:
* Email. The email address of the user.
* Features. Shows if IMAP or POP3 access is enabled.
* Storage quota. Shows how much assigned storage has been consumed.
* Sending Quota. The sending quota is the limit of messages a single user can send per day.
* Comment. A desription for the user.
* Created. Date when the user was created.
* Last edit. Last date when the user was modified.
.. _webadministration_add_user: .. _webadministration_add_user:
@ -334,7 +349,7 @@ For adding a new user the following options can be configured.
* Enabled. Tick this checkbox to enable the user account. When an user is disabled, the user is unable to login to the Admin GUI or webmail or access his email via IMAP/POP3 or send mail. * Enabled. Tick this checkbox to enable the user account. When an user is disabled, the user is unable to login to the Admin GUI or webmail or access his email via IMAP/POP3 or send mail.
The email inbox of the user is still retained. This option can be used to temporarily suspend an user account. The email inbox of the user is still retained. This option can be used to temporarily suspend an user account.
* Quota. The maximum quota for the user's email box. * Storage Quota. The maximum quota for the user's email box.
* Allow IMAP access. When ticked, allows email retrieval via the IMAP protocol. * Allow IMAP access. When ticked, allows email retrieval via the IMAP protocol.

@ -62,6 +62,11 @@ ANTIVIRUS={{ antivirus_enabled or 'none' }}
# Max attachment size will be 33% smaller # Max attachment size will be 33% smaller
MESSAGE_SIZE_LIMIT={{ message_size_limit or '50000000' }} MESSAGE_SIZE_LIMIT={{ message_size_limit or '50000000' }}
# Message rate limit (per user)
{% if message_ratelimit_pd > '0' %}
MESSAGE_RATELIMIT={{ message_ratelimit_pd }}/day
{% endif %}
# Networks granted relay permissions # Networks granted relay permissions
# Use this with care, all hosts in this networks will be able to send mail without authentication! # Use this with care, all hosts in this networks will be able to send mail without authentication!
RELAYNETS= RELAYNETS=

@ -55,6 +55,13 @@ Or in plain english: if receivers start to classify your mail as spam, this post
</p> </p>
</div> </div>
<div class="form-group">
<label>Outgoing message rate limit (per user)</label>
<!-- Validates number input only -->
<p><input class="form-control" style="width: 7%; display: inline;" type="number" name="message_ratelimit_pd" value="200" required > / day
</p>
</div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<label class="form-check-label"> <label class="form-check-label">
<input class="form-check-input" type="checkbox" name="disable_statistics" value="True"> <input class="form-check-input" type="checkbox" name="disable_statistics" value="True">

@ -0,0 +1 @@
Add sending quotas per user

@ -0,0 +1,5 @@
Add support for ECDSA certificates when letsencrypt is used. This means dropping compatibility for android < 4.1.1
Add LETSENCRYPT_SHORTCHAIN to your configuration to avoid sending ISRG Root X1 (this will break compatibility with android < 7.1.1)
Disable AUTH command on port 25
Disable TLS tickets, reconfigure the cache to improve Forward Secrecy
Prevent clear-text credentials from being sent to relays
Loading…
Cancel
Save