mirror of
https://github.com/optim-enterprises-bv/Mailu.git
synced 2025-11-01 10:37:49 +00:00
2042: Add MESSAGE_RATELIMIT_EXEMPTION r=mergify[bot] a=nextgens ## What type of PR? Enhancement ## What does this PR do? Add a new knob called ```MESSAGE_RATELIMIT_EXEMPTION```. ### Related issue(s) - #1774 ## Prerequisites Before we can consider review and merge, please make sure the following list is done and checked. If an entry in not applicable, you can check it or remove it from the list. - [x] In case of feature or enhancement: documentation updated accordingly - [ ] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Florent Daigniere <nextgens@freenetproject.org>
185 lines
6.5 KiB
Python
185 lines
6.5 KiB
Python
import os
|
|
|
|
from datetime import timedelta
|
|
from socrate import system
|
|
import ipaddress
|
|
|
|
DEFAULT_CONFIG = {
|
|
# Specific to the admin UI
|
|
'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
|
|
'BABEL_DEFAULT_LOCALE': 'en',
|
|
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
|
'RATELIMIT_STORAGE_URL': '',
|
|
'QUOTA_STORAGE_URL': '',
|
|
'DEBUG': False,
|
|
'DOMAIN_REGISTRATION': False,
|
|
'TEMPLATES_AUTO_RELOAD': True,
|
|
'MEMORY_SESSIONS': False,
|
|
# Database settings
|
|
'DB_FLAVOR': None,
|
|
'DB_USER': 'mailu',
|
|
'DB_PW': None,
|
|
'DB_HOST': 'database',
|
|
'DB_NAME': 'mailu',
|
|
'SQLITE_DATABASE_FILE':'data/main.db',
|
|
'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
|
|
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
|
|
# Statistics management
|
|
'INSTANCE_ID_PATH': '/data/instance',
|
|
'STATS_ENDPOINT': '18.{}.stats.mailu.io',
|
|
# Common configuration variables
|
|
'SECRET_KEY': 'changeMe',
|
|
'DOMAIN': 'mailu.io',
|
|
'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
|
|
'POSTMASTER': 'postmaster',
|
|
'WILDCARD_SENDERS': '',
|
|
'TLS_FLAVOR': 'cert',
|
|
'INBOUND_TLS_ENFORCE': False,
|
|
'DEFER_ON_TLS_ERROR': True,
|
|
'AUTH_RATELIMIT_IP': '60/hour',
|
|
'AUTH_RATELIMIT_IP_V4_MASK': 24,
|
|
'AUTH_RATELIMIT_IP_V6_MASK': 56,
|
|
'AUTH_RATELIMIT_USER': '100/day',
|
|
'AUTH_RATELIMIT_EXEMPTION': '',
|
|
'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
|
|
'DISABLE_STATISTICS': False,
|
|
# Mail settings
|
|
'DMARC_RUA': None,
|
|
'DMARC_RUF': None,
|
|
'WELCOME': False,
|
|
'WELCOME_SUBJECT': 'Dummy welcome topic',
|
|
'WELCOME_BODY': 'Dummy welcome body',
|
|
'DKIM_SELECTOR': 'dkim',
|
|
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
|
'DEFAULT_QUOTA': 1000000000,
|
|
'MESSAGE_RATELIMIT': '200/day',
|
|
'MESSAGE_RATELIMIT_EXEMPTION': '',
|
|
'RECIPIENT_DELIMITER': '',
|
|
# Web settings
|
|
'SITENAME': 'Mailu',
|
|
'WEBSITE': 'https://mailu.io',
|
|
'ADMIN' : 'none',
|
|
'WEB_ADMIN': '/admin',
|
|
'WEB_WEBMAIL': '/webmail',
|
|
'WEBMAIL': 'none',
|
|
'RECAPTCHA_PUBLIC_KEY': '',
|
|
'RECAPTCHA_PRIVATE_KEY': '',
|
|
'LOGO_URL': None,
|
|
'LOGO_BACKGROUND': None,
|
|
# Advanced settings
|
|
'LOG_LEVEL': 'WARNING',
|
|
'SESSION_KEY_BITS': 128,
|
|
'SESSION_LIFETIME': 24,
|
|
'SESSION_COOKIE_SECURE': True,
|
|
'CREDENTIAL_ROUNDS': 12,
|
|
'TZ': 'Etc/UTC',
|
|
# Host settings
|
|
'HOST_IMAP': 'imap',
|
|
'HOST_LMTP': 'imap:2525',
|
|
'HOST_POP3': 'imap',
|
|
'HOST_SMTP': 'smtp',
|
|
'HOST_AUTHSMTP': 'smtp',
|
|
'HOST_ADMIN': 'admin',
|
|
'HOST_WEBMAIL': 'webmail',
|
|
'HOST_WEBDAV': 'webdav:5232',
|
|
'HOST_REDIS': 'redis',
|
|
'HOST_FRONT': 'front',
|
|
'SUBNET': '192.168.203.0/24',
|
|
'SUBNET6': None,
|
|
'POD_ADDRESS_RANGE': None
|
|
}
|
|
|
|
class ConfigManager(dict):
|
|
""" Naive configuration manager that uses environment only
|
|
"""
|
|
|
|
DB_TEMPLATES = {
|
|
'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
|
|
'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
|
|
'mysql': 'mysql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}'
|
|
}
|
|
|
|
def __init__(self):
|
|
self.config = dict()
|
|
|
|
def get_host_address(self, name):
|
|
# if MYSERVICE_ADDRESS is defined, use this
|
|
if '{}_ADDRESS'.format(name) in os.environ:
|
|
return os.environ.get('{}_ADDRESS'.format(name))
|
|
# otherwise use the host name and resolve it
|
|
return system.resolve_address(self.config['HOST_{}'.format(name)])
|
|
|
|
def resolve_hosts(self):
|
|
self.config["IMAP_ADDRESS"] = self.get_host_address("IMAP")
|
|
self.config["POP3_ADDRESS"] = self.get_host_address("POP3")
|
|
self.config["AUTHSMTP_ADDRESS"] = self.get_host_address("AUTHSMTP")
|
|
self.config["SMTP_ADDRESS"] = self.get_host_address("SMTP")
|
|
self.config["REDIS_ADDRESS"] = self.get_host_address("REDIS")
|
|
if self.config["WEBMAIL"] != "none":
|
|
self.config["WEBMAIL_ADDRESS"] = self.get_host_address("WEBMAIL")
|
|
|
|
def __get_env(self, key, value):
|
|
key_file = key + "_FILE"
|
|
if key_file in os.environ:
|
|
with open(os.environ.get(key_file)) as file:
|
|
value_from_file = file.read()
|
|
return value_from_file.strip()
|
|
else:
|
|
return os.environ.get(key, value)
|
|
|
|
def __coerce_value(self, value):
|
|
if isinstance(value, str) and value.lower() in ('true','yes'):
|
|
return True
|
|
elif isinstance(value, str) and value.lower() in ('false', 'no'):
|
|
return False
|
|
return value
|
|
|
|
def init_app(self, app):
|
|
self.config.update(app.config)
|
|
# get environment variables
|
|
self.config.update({
|
|
key: self.__coerce_value(self.__get_env(key, value))
|
|
for key, value in DEFAULT_CONFIG.items()
|
|
})
|
|
self.resolve_hosts()
|
|
|
|
# automatically set the sqlalchemy string
|
|
if self.config['DB_FLAVOR']:
|
|
template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
|
|
self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
|
|
|
|
self.config['RATELIMIT_STORAGE_URL'] = 'redis://{0}/2'.format(self.config['REDIS_ADDRESS'])
|
|
self.config['QUOTA_STORAGE_URL'] = 'redis://{0}/1'.format(self.config['REDIS_ADDRESS'])
|
|
self.config['SESSION_STORAGE_URL'] = 'redis://{0}/3'.format(self.config['REDIS_ADDRESS'])
|
|
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
|
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME']))
|
|
hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
|
|
self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)
|
|
self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
|
|
self.config['HOSTNAMES'] = ','.join(hostnames)
|
|
self.config['HOSTNAME'] = hostnames[0]
|
|
# update the app config itself
|
|
app.config = self
|
|
|
|
def setdefault(self, key, value):
|
|
if key not in self.config:
|
|
self.config[key] = value
|
|
return self.config[key]
|
|
|
|
def get(self, *args):
|
|
return self.config.get(*args)
|
|
|
|
def keys(self):
|
|
return self.config.keys()
|
|
|
|
def __getitem__(self, key):
|
|
return self.config.get(key)
|
|
|
|
def __setitem__(self, key, value):
|
|
self.config[key] = value
|
|
|
|
def __contains__(self, key):
|
|
return key in self.config
|