mirror of
https://github.com/optim-enterprises-bv/Mailu-OIDC.git
synced 2025-10-29 09:12:41 +00:00
Merge remote-tracking branch 'upstream/2024.06' into oidc
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ pip-selfcheck.json
|
||||
/docs/lib*
|
||||
/docs/bin
|
||||
/docs/include
|
||||
/docs/contributors/mailu-network-diagram.svg
|
||||
/docs/_build
|
||||
/.env
|
||||
/.venv
|
||||
|
||||
@@ -23,7 +23,7 @@ RUN set -euxo pipefail \
|
||||
RUN echo $VERSION >/version
|
||||
|
||||
#EXPOSE 8080/tcp
|
||||
HEALTHCHECK CMD curl -skfLo /dev/null http://localhost:8080/ping
|
||||
HEALTHCHECK CMD curl -m3 -skfLo /dev/null http://localhost:8080/ping
|
||||
|
||||
VOLUME ["/data","/dkim"]
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ function sha1(string) {
|
||||
}
|
||||
|
||||
function hibpCheck(pwd) {
|
||||
if (pwd === null || pwd === undefined || pwd.length === 0) {
|
||||
return;
|
||||
}
|
||||
// We hash the pwd first
|
||||
sha1(pwd).then(function(hash){
|
||||
// We send the first 5 chars of the hash to hibp's API
|
||||
|
||||
@@ -18,7 +18,11 @@ STATUSES = {
|
||||
"sieve": "AuthFailed"
|
||||
}),
|
||||
"encryption": ("Must issue a STARTTLS command first", {
|
||||
"smtp": "530 5.7.0"
|
||||
"imap": "PRIVACYREQUIRED",
|
||||
"smtp": "530 5.7.0",
|
||||
"submission": "530 5.7.0",
|
||||
"pop3": "-ERR Authentication canceled.",
|
||||
"sieve": "ENCRYPT-NEEDED"
|
||||
}),
|
||||
"ratelimit": ("Temporary authentication failure (rate-limit)", {
|
||||
"imap": "LIMIT",
|
||||
@@ -68,7 +72,7 @@ def handle_authentication(headers):
|
||||
# Incoming mail, no authentication
|
||||
if method in ['', 'none'] and protocol in ['smtp', 'lmtp']:
|
||||
server, port = get_server(protocol, False)
|
||||
if app.config["INBOUND_TLS_ENFORCE"]:
|
||||
if app.config["INBOUND_TLS_ENFORCE"] and protocol == 'smtp':
|
||||
if "Auth-SSL" in headers and headers["Auth-SSL"] == "on":
|
||||
return {
|
||||
"Auth-Status": "OK",
|
||||
@@ -91,20 +95,14 @@ def handle_authentication(headers):
|
||||
# Authenticated user
|
||||
elif method in ['plain', 'login']:
|
||||
is_valid_user = False
|
||||
# According to RFC2616 section 3.7.1 and PEP 3333, HTTP headers should
|
||||
# be ASCII and are generally considered ISO8859-1. However when passing
|
||||
# the password, nginx does not transcode the input UTF string, thus
|
||||
# we need to manually decode.
|
||||
raw_user_email = urllib.parse.unquote(headers["Auth-User"])
|
||||
raw_password = urllib.parse.unquote(headers["Auth-Pass"])
|
||||
user_email = 'invalid'
|
||||
password = 'invalid'
|
||||
try:
|
||||
user_email = raw_user_email.encode("iso8859-1").decode("utf8")
|
||||
password = raw_password.encode("iso8859-1").decode("utf8")
|
||||
user_email = urllib.parse.unquote(headers["Auth-User"])
|
||||
password = urllib.parse.unquote(headers["Auth-Pass"])
|
||||
ip = urllib.parse.unquote(headers["Client-Ip"])
|
||||
except:
|
||||
app.logger.warn(f'Received undecodable user/password from nginx: {raw_user_email!r}/{raw_password!r}')
|
||||
app.logger.warn(f'Received undecodable user/password from front: {headers.get("Auth-User", "")!r}')
|
||||
else:
|
||||
try:
|
||||
user = models.User.query.get(user_email) if '@' in user_email else None
|
||||
|
||||
@@ -29,7 +29,6 @@ def nginx_authentication():
|
||||
response.headers['Auth-Status'] = status
|
||||
response.headers['Auth-Error-Code'] = code
|
||||
return response
|
||||
raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''
|
||||
headers = nginx.handle_authentication(flask.request.headers)
|
||||
response = flask.Response()
|
||||
for key, value in headers.items():
|
||||
@@ -50,14 +49,8 @@ def nginx_authentication():
|
||||
if not is_port_25:
|
||||
utils.limiter.exempt_ip_from_ratelimits(client_ip)
|
||||
elif is_valid_user:
|
||||
password = None
|
||||
try:
|
||||
password = raw_password.encode("iso8859-1").decode("utf8")
|
||||
except:
|
||||
app.logger.warn(f'Received undecodable password for {username} from nginx: {raw_password!r}')
|
||||
utils.limiter.rate_limit_user(username, client_ip, password=None)
|
||||
else:
|
||||
utils.limiter.rate_limit_user(username, client_ip, password=password)
|
||||
password = urllib.parse.unquote(headers.get('Auth-Pass', ''))
|
||||
utils.limiter.rate_limit_user(username, client_ip, password=password)
|
||||
elif not is_from_webmail:
|
||||
utils.limiter.rate_limit_ip(client_ip, username)
|
||||
return response
|
||||
|
||||
@@ -279,7 +279,7 @@ class Domain(Base):
|
||||
f'_{proto}._tcp.{self.name}. 600 IN SRV {prio} 1 {port} {hostname}.' if port in ports else f'_{proto}._tcp.{self.name}. 600 IN SRV 0 0 0 .'
|
||||
for proto, port, prio
|
||||
in protocols
|
||||
]+[f'autoconfig.{self.name}. 600 IN CNAME {hostname}.']
|
||||
]+[f'autoconfig.{self.name}. 600 IN CNAME {hostname}.', f'autodiscover.{self.name}. 600 IN CNAME {hostname}.']
|
||||
|
||||
@cached_property
|
||||
def dns_tlsa(self):
|
||||
@@ -680,7 +680,7 @@ in clear-text regardless of the presence of the cache.
|
||||
set() containing the sessions to keep
|
||||
"""
|
||||
self.password = password if raw else User.get_password_context().hash(password)
|
||||
if keep_sessions is not True:
|
||||
if keep_sessions is not True and self.email is not None:
|
||||
utils.MailuSessionExtension.prune_sessions(uid=self.email, keep=keep_sessions)
|
||||
|
||||
def get_managed_domains(self):
|
||||
|
||||
@@ -24,7 +24,7 @@ def user_create(domain_name):
|
||||
flask.url_for('.user_list', domain_name=domain.name))
|
||||
form = forms.UserForm()
|
||||
form.pw.validators = [wtforms.validators.DataRequired()]
|
||||
form.quota_bytes.default = app.config['DEFAULT_QUOTA']
|
||||
form.quota_bytes.default = int(app.config['DEFAULT_QUOTA'])
|
||||
if domain.max_quota_bytes:
|
||||
form.quota_bytes.validators = [
|
||||
wtforms.validators.NumberRange(max=domain.max_quota_bytes)]
|
||||
@@ -93,12 +93,12 @@ def user_settings(user_email):
|
||||
form = forms.UserSettingsForm(obj=user)
|
||||
utils.formatCSVField(form.forward_destination)
|
||||
if form.validate_on_submit():
|
||||
if form.forward_enabled.data and (form.forward_destination.data in ['', None] or type(form.forward_destination.data) is list):
|
||||
user.forward_enabled = bool(flask.request.form.get('forward_enabled', False))
|
||||
if user.forward_enabled and not form.forward_destination.data:
|
||||
flask.flash('Destination email address is missing', 'error')
|
||||
user.forward_enabled = True
|
||||
return flask.render_template('user/settings.html', form=form, user=user)
|
||||
if form.forward_enabled.data:
|
||||
form.forward_destination.data = form.forward_destination.data.replace(" ","").split(",")
|
||||
return flask.redirect(
|
||||
flask.url_for('.user_settings', user_email=user_email))
|
||||
form.forward_destination.data = form.forward_destination.data.replace(" ","").split(",")
|
||||
form.populate_obj(user)
|
||||
models.db.session.commit()
|
||||
form.forward_destination.data = ", ".join(form.forward_destination.data)
|
||||
@@ -107,8 +107,9 @@ def user_settings(user_email):
|
||||
return flask.redirect(
|
||||
flask.url_for('.user_list', domain_name=user.domain.name))
|
||||
elif form.is_submitted() and not form.validate():
|
||||
user.forward_enabled = form.forward_enabled.data
|
||||
return flask.render_template('user/settings.html', form=form, user=user)
|
||||
flask.flash('Error validating the form', 'error')
|
||||
return flask.redirect(
|
||||
flask.url_for('.user_settings', user_email=user_email))
|
||||
return flask.render_template('user/settings.html', form=form, user=user)
|
||||
|
||||
def _process_password_change(form, user_email):
|
||||
|
||||
@@ -698,6 +698,7 @@ def isBadOrPwned(form):
|
||||
|
||||
def formatCSVField(field):
|
||||
if not field.data:
|
||||
field.data = ''
|
||||
return
|
||||
if isinstance(field.data,str):
|
||||
data = field.data.replace(" ","").split(",")
|
||||
|
||||
@@ -64,7 +64,7 @@ test_unsupported()
|
||||
|
||||
cmdline = [
|
||||
"gunicorn",
|
||||
"--threads", f"{os.cpu_count()}",
|
||||
"--threads", os.environ.get('CPU_COUNT', '1'),
|
||||
# If SUBNET6 is defined, gunicorn must listen on IPv6 as well as IPv4
|
||||
"-b", f"{'[::]' if os.environ.get('SUBNET6') else '0.0.0.0'}:8080",
|
||||
"--logger-class mailu.Logger",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# base system image (intermediate)
|
||||
# Note when updating the alpine tag, first manually run the workflow .github/workflows/mirror.yml.
|
||||
# Just run the workflow with the tag that must be synchronised.
|
||||
ARG DISTRO=ghcr.io/mailu/alpine:3.20
|
||||
ARG DISTRO=ghcr.io/mailu/alpine:3.20.3
|
||||
FROM $DISTRO as system
|
||||
|
||||
ENV TZ=Etc/UTC LANG=C.UTF-8
|
||||
|
||||
@@ -31,30 +31,29 @@ def _coerce_value(value):
|
||||
|
||||
class LogFilter(object):
|
||||
def __init__(self, stream, re_patterns):
|
||||
self.stream = stream
|
||||
if isinstance(re_patterns, list):
|
||||
self.pattern = re.compile('|'.join([fr'(?:{pattern})' for pattern in re_patterns]))
|
||||
elif isinstance(re_patterns, str):
|
||||
self.pattern = re.compile(re_patterns)
|
||||
else:
|
||||
self.pattern = re_patterns
|
||||
self.found = False
|
||||
self.stream = stream
|
||||
self.pattern = re.compile(b'|'.join([b''.join([b'(?:', pattern, b')']) for pattern in re_patterns]))
|
||||
self.buffer = b''
|
||||
|
||||
def __getattr__(self, attr_name):
|
||||
return getattr(self.stream, attr_name)
|
||||
|
||||
def write(self, data):
|
||||
if data == '\n' and self.found:
|
||||
self.found = False
|
||||
else:
|
||||
if not self.pattern.search(data):
|
||||
self.stream.write(data)
|
||||
if type(data) is str:
|
||||
data = data.encode('utf-8')
|
||||
self.buffer += data
|
||||
while b'\n' in self.buffer:
|
||||
line, cr, rest = self.buffer.partition(b'\n')
|
||||
if not self.pattern.search(line):
|
||||
self.stream.buffer.write(line)
|
||||
self.stream.buffer.write(cr)
|
||||
self.stream.flush()
|
||||
else:
|
||||
# caught bad pattern
|
||||
self.found = True
|
||||
self.buffer = rest
|
||||
|
||||
def flush(self):
|
||||
# write out buffer on flush even if it's not a complete line
|
||||
if self.buffer and not self.pattern.search(self.buffer):
|
||||
self.stream.buffer.write(self.buffer)
|
||||
self.stream.flush()
|
||||
|
||||
def _is_compatible_with_hardened_malloc():
|
||||
@@ -100,7 +99,7 @@ def set_env(required_secrets=[], log_filters=[]):
|
||||
for secret in required_secrets:
|
||||
os.environ[f'{secret}_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray(secret, 'utf-8'), 'sha256').hexdigest()
|
||||
|
||||
os.system('find /run -xdev -type f -name \*.pid -print -delete')
|
||||
os.system(r'find /run -xdev -type f -name \*.pid -print -delete')
|
||||
|
||||
return {
|
||||
key: _coerce_value(os.environ.get(key, value))
|
||||
@@ -108,8 +107,41 @@ def set_env(required_secrets=[], log_filters=[]):
|
||||
}
|
||||
|
||||
def clean_env():
|
||||
""" remove all secret keys """
|
||||
""" remove all secret keys, normalize PROXY_PROTOCOL """
|
||||
[os.environ.pop(key, None) for key in os.environ.keys() if key.endswith("_KEY")]
|
||||
# Configure PROXY_PROTOCOL
|
||||
PROTO_MAIL=['25', '110', '995', '143', '993', '587', '465', '4190']
|
||||
PROTO_ALL_BUT_HTTP=PROTO_MAIL.copy()
|
||||
PROTO_ALL_BUT_HTTP.extend(['443'])
|
||||
PROTO_ALL=PROTO_ALL_BUT_HTTP.copy()
|
||||
PROTO_ALL.extend(['80'])
|
||||
for item in os.environ.get('PROXY_PROTOCOL', '').split(','):
|
||||
if item.isdigit():
|
||||
os.environ[f'PROXY_PROTOCOL_{item}']='True'
|
||||
elif item == 'mail':
|
||||
for p in PROTO_MAIL: os.environ[f'PROXY_PROTOCOL_{p}']='True'
|
||||
elif item == 'all-but-http':
|
||||
for p in PROTO_ALL_BUT_HTTP: os.environ[f'PROXY_PROTOCOL_{p}']='True'
|
||||
elif item == 'all':
|
||||
for p in PROTO_ALL: os.environ[f'PROXY_PROTOCOL_{p}']='True'
|
||||
elif item == '':
|
||||
pass
|
||||
else:
|
||||
log.error(f'Not sure what to do with {item} in PROXY_PROTOCOL ({args.get("PROXY_PROTOCOL")})')
|
||||
|
||||
PORTS_REQUIRING_TLS=['443', '465', '993', '995']
|
||||
ALL_PORTS='25,80,443,465,993,995,4190'
|
||||
for item in os.environ.get('PORTS', ALL_PORTS).split(','):
|
||||
if item in PORTS_REQUIRING_TLS and os.environ.get('TLS_FLAVOR','') == 'notls':
|
||||
continue
|
||||
os.environ[f'PORT_{item}']='True'
|
||||
|
||||
if os.environ.get('TLS_FLAVOR', '') != 'notls':
|
||||
for item in os.environ.get('TLS', ALL_PORTS).split(','):
|
||||
if item in PORTS_REQUIRING_TLS:
|
||||
os.environ[f'TLS_{item}']='True'
|
||||
if 'CPU_COUNT' not in os.environ:
|
||||
os.environ['CPU_COUNT'] = str(os.cpu_count())
|
||||
|
||||
def drop_privs_to(username='mailu'):
|
||||
pwnam = getpwnam(username)
|
||||
@@ -127,7 +159,7 @@ def forward_text_lines(src, dst):
|
||||
|
||||
# runs a process and passes its standard/error output to the standard/error output of the current python script
|
||||
def run_process_and_forward_output(cmd):
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
stdout_thread = threading.Thread(target=forward_text_lines, args=(process.stdout, sys.stdout))
|
||||
stdout_thread.daemon = True
|
||||
@@ -137,4 +169,7 @@ def run_process_and_forward_output(cmd):
|
||||
stderr_thread.daemon = True
|
||||
stderr_thread.start()
|
||||
|
||||
process.wait()
|
||||
rc = process.wait()
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
return rc
|
||||
|
||||
@@ -137,12 +137,22 @@ service imap-login {
|
||||
inet_listener imap {
|
||||
port = 143
|
||||
}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
|
||||
service pop3-login {
|
||||
inet_listener pop3 {
|
||||
port = 110
|
||||
}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
|
||||
###############
|
||||
@@ -166,6 +176,11 @@ service managesieve-login {
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
|
||||
protocol sieve {
|
||||
|
||||
@@ -7,7 +7,9 @@ import multiprocessing
|
||||
from podop import run_server
|
||||
from socrate import system, conf
|
||||
|
||||
system.set_env(log_filters=[r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$'])
|
||||
system.set_env(log_filters=[
|
||||
rb'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$'
|
||||
])
|
||||
|
||||
def start_podop():
|
||||
system.drop_privs_to('mail')
|
||||
|
||||
@@ -29,7 +29,7 @@ RUN echo $VERSION >/version
|
||||
|
||||
EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 4190/tcp
|
||||
# EXPOSE 10025/tcp 10143/tcp 14190/tcp
|
||||
HEALTHCHECK --start-period=60s CMD curl -skfLo /dev/null http://127.0.0.1:10204/health && kill -0 `cat /run/dovecot/master.pid`
|
||||
HEALTHCHECK --start-period=60s CMD curl -m3 -skfLo /dev/null http://127.0.0.1:10204/health && kill -0 `cat /run/dovecot/master.pid`
|
||||
|
||||
VOLUME ["/certs", "/overrides"]
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ class ChangeHandler(FileSystemEventHandler):
|
||||
if exists("/var/run/nginx.pid"):
|
||||
print("Reloading a running nginx")
|
||||
system("nginx -s reload")
|
||||
if os.path.exists("/run/dovecot/master.pid"):
|
||||
if exists("/run/dovecot/master.pid"):
|
||||
print("Reloading a running dovecot")
|
||||
os.system("doveadm reload")
|
||||
system("doveadm reload")
|
||||
|
||||
@staticmethod
|
||||
def reexec_config():
|
||||
|
||||
@@ -66,6 +66,9 @@ http {
|
||||
listen [::]:80{% if PROXY_PROTOCOL_80 %} proxy_protocol{% endif %};
|
||||
{% endif %}
|
||||
{% if TLS_FLAVOR in ['letsencrypt', 'mail-letsencrypt'] %}
|
||||
location ^~ /.well-known/acme-challenge/testing {
|
||||
return 204;
|
||||
}
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
proxy_pass http://127.0.0.1:8008;
|
||||
}
|
||||
@@ -95,6 +98,7 @@ http {
|
||||
set $webdav {{ WEBDAV_ADDRESS }}:5232;
|
||||
{% endif %}
|
||||
client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }};
|
||||
http2 on;
|
||||
|
||||
# Listen on HTTP only in kubernetes or behind reverse proxy
|
||||
{% if TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %}
|
||||
@@ -109,7 +113,6 @@ http {
|
||||
listen 443 ssl{% if PROXY_PROTOCOL_443 %} proxy_protocol{% endif %};
|
||||
{% if SUBNET6 %}
|
||||
listen [::]:443 ssl{% if PROXY_PROTOCOL_443 %} proxy_protocol{% endif %};
|
||||
http2 on;
|
||||
{% endif %}
|
||||
|
||||
include /etc/nginx/tls.conf;
|
||||
@@ -159,6 +162,9 @@ http {
|
||||
}
|
||||
|
||||
{% if TLS_FLAVOR in ['letsencrypt', 'mail-letsencrypt'] %}
|
||||
location ^~ /.well-known/acme-challenge/testing {
|
||||
return 204;
|
||||
}
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
proxy_pass http://127.0.0.1:8008;
|
||||
}
|
||||
|
||||
@@ -70,38 +70,6 @@ with open("/etc/resolv.conf") as handle:
|
||||
resolver = content[content.index("nameserver") + 1]
|
||||
args["RESOLVER"] = f"[{resolver}]" if ":" in resolver else resolver
|
||||
|
||||
# Configure PROXY_PROTOCOL
|
||||
PROTO_MAIL=['25', '110', '995', '143', '993', '587', '465', '4190']
|
||||
PROTO_ALL_BUT_HTTP=PROTO_MAIL.copy()
|
||||
PROTO_ALL_BUT_HTTP.extend(['443'])
|
||||
PROTO_ALL=PROTO_ALL_BUT_HTTP.copy()
|
||||
PROTO_ALL.extend(['80'])
|
||||
for item in args.get('PROXY_PROTOCOL', '').split(','):
|
||||
if item.isdigit():
|
||||
args[f'PROXY_PROTOCOL_{item}']=True
|
||||
elif item == 'mail':
|
||||
for p in PROTO_MAIL: args[f'PROXY_PROTOCOL_{p}']=True
|
||||
elif item == 'all-but-http':
|
||||
for p in PROTO_ALL_BUT_HTTP: args[f'PROXY_PROTOCOL_{p}']=True
|
||||
elif item == 'all':
|
||||
for p in PROTO_ALL: args[f'PROXY_PROTOCOL_{p}']=True
|
||||
elif item == '':
|
||||
pass
|
||||
else:
|
||||
log.error(f'Not sure what to do with {item} in PROXY_PROTOCOL ({args.get("PROXY_PROTOCOL")})')
|
||||
|
||||
PORTS_REQUIRING_TLS=['443', '465', '993', '995']
|
||||
ALL_PORTS='25,80,443,465,993,995,4190'
|
||||
for item in args.get('PORTS', ALL_PORTS).split(','):
|
||||
if item in PORTS_REQUIRING_TLS and args['TLS_FLAVOR'] == 'notls':
|
||||
continue
|
||||
args[f'PORT_{item}']=True
|
||||
|
||||
if args['TLS_FLAVOR'] != 'notls':
|
||||
for item in args.get('TLS', ALL_PORTS).split(','):
|
||||
if item in PORTS_REQUIRING_TLS:
|
||||
args[f'TLS_{item}']=True
|
||||
|
||||
# TLS configuration
|
||||
cert_name = args.get("TLS_CERT_FILENAME", "cert.pem")
|
||||
keypair_name = args.get("TLS_KEYPAIR_FILENAME", "key.pem")
|
||||
|
||||
@@ -10,18 +10,28 @@ local http_client = dovecot.http.client {
|
||||
max_attempts = 3;
|
||||
}
|
||||
|
||||
-- on the other end we use urllib.parse.unquote()
|
||||
function urlEncode(str)
|
||||
return str:gsub("[^%w_.-~]", function(c)
|
||||
return string.format("%%%02X", string.byte(c))
|
||||
end)
|
||||
end
|
||||
|
||||
function auth_passdb_lookup(req)
|
||||
local auth_request = http_client:request {
|
||||
url = "http://{{ ADMIN_ADDRESS }}:8080/internal/auth/email";
|
||||
}
|
||||
auth_request:add_header('Auth-Port', req.local_port)
|
||||
auth_request:add_header('Auth-User', req.user)
|
||||
local user = urlEncode(req.user)
|
||||
auth_request:add_header('Auth-User', user)
|
||||
if req.password ~= nil
|
||||
then
|
||||
auth_request:add_header('Auth-Pass', req.password)
|
||||
local password = urlEncode(req.password)
|
||||
auth_request:add_header('Auth-Pass', password)
|
||||
end
|
||||
auth_request:add_header('Auth-Protocol', req.service)
|
||||
auth_request:add_header('Client-IP', req.remote_ip)
|
||||
local client_ip = urlEncode(req.remote_ip)
|
||||
auth_request:add_header('Client-Ip', client_ip)
|
||||
auth_request:add_header('Client-Port', req.remote_port)
|
||||
auth_request:add_header('Auth-SSL', req.secured)
|
||||
auth_request:add_header('Auth-Method', req.mechanism)
|
||||
|
||||
@@ -87,6 +87,11 @@ service managesieve-login {
|
||||
inet_listener sieve-webmail {
|
||||
port = 14190
|
||||
}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
@@ -114,6 +119,11 @@ service imap-login {
|
||||
inet_listener imap-webmail {
|
||||
port = 10143
|
||||
}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
|
||||
service pop3-login {
|
||||
@@ -132,6 +142,11 @@ service pop3-login {
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
|
||||
recipient_delimiter = {{ RECIPIENT_DELIMITER }}
|
||||
@@ -161,4 +176,11 @@ service submission-login {
|
||||
inet_listener submission-webmail {
|
||||
port = 10025
|
||||
}
|
||||
service_count = 0
|
||||
client_limit = 25000
|
||||
process_min_avail = {{ CPU_COUNT }}
|
||||
process_limit = {{ CPU_COUNT }}
|
||||
vsz_limit = 256M
|
||||
}
|
||||
|
||||
!include_try /overrides/dovecot/proxy.conf
|
||||
|
||||
@@ -6,8 +6,6 @@ import requests
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from threading import Thread
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
|
||||
log.basicConfig(stream=sys.stderr, level="WARNING")
|
||||
hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))
|
||||
@@ -22,6 +20,7 @@ command = [
|
||||
"--preferred-challenges", "http", "--http-01-port", "8008",
|
||||
"--keep-until-expiring",
|
||||
"--allow-subset-of-names",
|
||||
"--key-type", "rsa",
|
||||
"--renew-with-new-domains",
|
||||
"--config-dir", "/certs/letsencrypt",
|
||||
"--post-hook", "/config.py"
|
||||
@@ -45,33 +44,21 @@ command2 = [
|
||||
# Wait for nginx to start
|
||||
time.sleep(5)
|
||||
|
||||
class MyRequestHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/.well-known/acme-challenge/testing':
|
||||
self.send_response(204)
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.send_header('Content-Type', 'text/plain')
|
||||
self.end_headers()
|
||||
|
||||
def serve_one_request():
|
||||
with HTTPServer(("127.0.0.1", 8008), MyRequestHandler) as server:
|
||||
server.handle_request()
|
||||
|
||||
# Run certbot every day
|
||||
while True:
|
||||
while True:
|
||||
hostname = os.environ['HOSTNAMES'].split(',')[0]
|
||||
target = f'http://{hostname}/.well-known/acme-challenge/testing'
|
||||
thread = Thread(target=serve_one_request)
|
||||
thread.start()
|
||||
r = requests.get(target)
|
||||
if r.status_code != 204:
|
||||
log.critical(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.")
|
||||
time.sleep(5)
|
||||
else:
|
||||
break
|
||||
thread.join()
|
||||
try:
|
||||
r = requests.get(target)
|
||||
if r.status_code != 204:
|
||||
log.critical(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.")
|
||||
time.sleep(5)
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
log.error(f"Exception while fetching {target}!", exc_info = e)
|
||||
time.sleep(15)
|
||||
|
||||
subprocess.call(command)
|
||||
subprocess.call(command2)
|
||||
|
||||
@@ -4,7 +4,9 @@ import os
|
||||
import subprocess
|
||||
from socrate import system
|
||||
|
||||
system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25|110|143|587|465|993|995)$')
|
||||
system.set_env(log_filters=[
|
||||
rb'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25|110|143|587|465|993|995)$'
|
||||
])
|
||||
|
||||
# Check if a stale pid file exists
|
||||
if os.path.exists("/var/run/nginx.pid"):
|
||||
|
||||
@@ -11,10 +11,10 @@ from podop import run_server
|
||||
from socrate import system, conf
|
||||
|
||||
system.set_env(log_filters=[
|
||||
r'(dis)?connect from localhost\[(\:\:1|127\.0\.0\.1)\]( quit=1 commands=1)?$',
|
||||
r'haproxy read\: short protocol header\: QUIT$',
|
||||
r'discarding EHLO keywords\: PIPELINING$'
|
||||
])
|
||||
rb'(dis)?connect from localhost\[(\:\:1|127\.0\.0\.1)\]( quit=1 commands=1)?$',
|
||||
rb'haproxy read\: short protocol header\: QUIT$',
|
||||
rb'discarding EHLO keywords\: PIPELINING$'
|
||||
])
|
||||
|
||||
os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid")
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ COPY start.py /
|
||||
RUN echo $VERSION >/version
|
||||
|
||||
#EXPOSE 11332/tcp 11334/tcp 11335/tcp
|
||||
HEALTHCHECK --start-period=350s CMD curl -skfLo /dev/null http://localhost:11334/
|
||||
HEALTHCHECK --start-period=350s CMD curl -m3 -skfLo /dev/null http://localhost:11334/
|
||||
|
||||
VOLUME ["/var/lib/rspamd"]
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ COPY . /docs
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; machine="$(uname -m)" \
|
||||
; deps="gcc musl-dev" \
|
||||
; deps="gcc musl-dev graphviz" \
|
||||
; [[ "${machine}" != x86_64 ]] && \
|
||||
deps="${deps} cargo" \
|
||||
; apk add --no-cache --virtual .build-deps ${deps} \
|
||||
@@ -17,7 +17,8 @@ RUN set -euxo pipefail \
|
||||
mkdir -p /root/.cargo/registry/index && \
|
||||
git clone --bare https://github.com/rust-lang/crates.io-index.git /root/.cargo/registry/index/github.com-1285ae84e5963aae \
|
||||
; pip3 install -r /requirements.txt \
|
||||
; mkdir -p /build/$VERSION \
|
||||
; mkdir -p /build/$VERSION/ \
|
||||
; dot -Tsvg /docs/mailu-network-diagram.dot -o /docs/contributors/mailu-network-diagram.svg \
|
||||
; sphinx-build -W /docs /build/$VERSION \
|
||||
; apk del .build-deps \
|
||||
; rm -rf /root/.cargo
|
||||
|
||||
@@ -113,7 +113,7 @@ The following steps have to be taken to configure an additional symbol (rule) th
|
||||
* soft reject: temporarily delay message (this is used, for instance, to greylist or rate-limit messages)
|
||||
|
||||
To move an email message to the Junk (Spam) folder, a score of 15 can be used in combination with the action "add header".
|
||||
The above example configuration will reject all emails send from domains that are listed in '/etc/rspamd/override.d/blacklist.inc'.
|
||||
The above example configuration will reject all emails send from domains that are listed in '/overrides/blacklist.inc'.
|
||||
|
||||
|
||||
2. In the Rspamd overrides folder create a map that contains the domains to be blocked. You can use # to add comments.
|
||||
@@ -137,12 +137,12 @@ The following steps have to be taken to configure an additional symbol (rule) th
|
||||
|
||||
The symbol is only displayed if the symbol has no pre-filter (action= line) configured. Changes made in this screen are not saved to the configuration file.
|
||||
|
||||
5. Check if the map is available. In rspamd webgui to to configuration. A map is available with the path:
|
||||
/etc/rspamd/override.d/blacklist.inc Senders domain part is on the local blacklist
|
||||
5. Check if the map is available. In rspamd webgui go to configuration, a map is available with the path:
|
||||
/overrides/blacklist.inc Senders domain part is on the local blacklist
|
||||
|
||||
.. image:: assets/screenshots/RspamdMapBlacklist.png
|
||||
|
||||
When clicking on this map, you can live-edit the map via the GUI. Changes are effective immediately. Only changes made to maps in the overrides folder are persistent. Changes made to other maps will be reverted when the Rspamd container is recreated. It is also possible to make direct changes to the map on filesystem. These changes are also effective immediately.
|
||||
When clicking on this map, you can live-edit the map via the GUI. Please note that only changes made to maps in the ``/overrides`` folder are persistent as changes made interractively though the GUI will be reverted when the Rspamd container is recreated. All changes (whether through the GUI or on the filesystem) are effective immediately.
|
||||
|
||||
For more information on using the multimap filter see the official `multimap documentation`_ of Rspamd.
|
||||
|
||||
|
||||
11
docs/contributors/firewalling.rst
Normal file
11
docs/contributors/firewalling.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
Firewalling
|
||||
===========
|
||||
|
||||
Network flows within Mailu
|
||||
--------------------------
|
||||
|
||||
The following diagram may prove useful in understanding how the different components interact.
|
||||
|
||||
.. image:: mailu-network-diagram.svg
|
||||
:target: ../_images/mailu-network-diagram.svg
|
||||
|
||||
@@ -256,8 +256,10 @@ correct syntax. The following file names will be taken as override configuration
|
||||
- For both ``postfix.cf`` and ``postfix.master``, you need to put one configuration per line, as they are fed line-by-line
|
||||
to postfix.
|
||||
- ``logrotate.conf`` as ``$ROOT/overrides/postfix/logrotate.conf`` - Replaces the logrotate.conf file used for rotating ``POSTFIX_LOG_FILE``.
|
||||
- `Dovecot`_ - ``dovecot.conf`` in dovecot sub-directory;
|
||||
- `Nginx`_ - All ``*.conf`` files in the ``nginx`` sub-directory;
|
||||
- `Dovecot`_ - ``dovecot.conf`` in dovecot sub-directory.
|
||||
- `Nginx`_ :
|
||||
- All ``*.conf`` files in the ``nginx`` sub-directory.
|
||||
- ``proxy.conf`` in the ``nginx/dovecot`` sub-directory.
|
||||
- `Rspamd`_ - All files in the ``rspamd`` sub-directory.
|
||||
- `Roundcube`_ - All ``*.inc.php`` files in the ``roundcube`` sub directory.
|
||||
|
||||
|
||||
@@ -81,3 +81,4 @@ the version of Mailu that you are running.
|
||||
contributors/database
|
||||
contributors/memo
|
||||
contributors/localization
|
||||
contributors/firewalling
|
||||
|
||||
138
docs/mailu-network-diagram.dot
Normal file
138
docs/mailu-network-diagram.dot
Normal file
@@ -0,0 +1,138 @@
|
||||
digraph mailu {
|
||||
label = "Mailu network flows";
|
||||
fontname = "arial";
|
||||
|
||||
node [shape = record; fontname = "arial"; fontsize = 8; style = filled; color = "#d3edea";];
|
||||
splines = "compound";
|
||||
// node [shape = "box"; fontsize = "10";];
|
||||
edge [fontsize = 8; arrowsize = 0.5;];
|
||||
|
||||
# Components
|
||||
internet [label = "Internet"; color = "red";];
|
||||
proxy [label = "Proxy (optional)"; color = "darkorange";];
|
||||
front [label="Front"; color="dodgerblue";];
|
||||
admin [label="Admin"; color="green"; fontcolor="white";];
|
||||
smtp [label="SMTP"; color="orchid";];
|
||||
redis [label="Redis"; color="turquoise";];
|
||||
antispam [label="Antispam"; color="magenta";];
|
||||
antivirus [label="Antivirus"; color="purple"; fontcolor="white";];
|
||||
imap [label="IMAP"; color="cyan";];
|
||||
webdav [label="WebDAV"; color="yellow";];
|
||||
webmail [label="Webmail"; color="darkgoldenrod";];
|
||||
fetchmail [label="Fetchmail"; color="chocolate";];
|
||||
oletools [label="Oletools"; color="limegreen";];
|
||||
fts_attachments [label="Tika"; color="sienna";];
|
||||
|
||||
rankdir=LR;
|
||||
{rank=min; internet};
|
||||
// {rank=3; proxy};
|
||||
// {rank=4; front};
|
||||
// {rank=same; admin smtp redis antispam antivirus imap};
|
||||
{rank=max; fetchmail};
|
||||
|
||||
# Proxy from internet
|
||||
|
||||
internet -> proxy [
|
||||
color="red";
|
||||
fontcolor="red";
|
||||
label = <
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLPADDING="1">
|
||||
<TR>
|
||||
<TD>80/tcp</TD>
|
||||
<TD>443/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>25/tcp</TD>
|
||||
<TD>465/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>587/tcp</TD>
|
||||
<TD>110/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>995/tcp</TD>
|
||||
<TD>143/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>993/tcp</TD>
|
||||
<TD>4190/tcp</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
>;
|
||||
];
|
||||
|
||||
# Front from proxy
|
||||
proxy -> front [
|
||||
color="darkorange";
|
||||
fontcolor="darkorange";
|
||||
label = <
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLPADDING="1">
|
||||
<TR>
|
||||
<TD>80/tcp</TD>
|
||||
<TD>443/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>25/tcp</TD>
|
||||
<TD>465/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>587/tcp</TD>
|
||||
<TD>110/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>995/tcp</TD>
|
||||
<TD>143/tcp</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD>993/tcp</TD>
|
||||
<TD>4190/tcp</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
>;
|
||||
];
|
||||
|
||||
front -> front [label = "8008/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> front [label = "8000/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> admin [label = "8080/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> imap [label = "4190/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> imap [label = "2525/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> imap [label = "143/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> imap [label = "110/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> smtp [label = "25/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> smtp [label = "10025/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> webmail [label = "80/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> antispam [label = "11334/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
front -> webdav [label = "5232/tcp"; color="dodgerblue"; fontcolor="dodgerblue";];
|
||||
|
||||
smtp -> admin [label = "8080/tcp"; color="orchid"; fontcolor="orchid";];
|
||||
smtp -> front [label = "2525/tcp"; color="orchid"; fontcolor="orchid";];
|
||||
smtp -> antispam [label = "11332/tcp"; color="orchid"; fontcolor="orchid";];
|
||||
|
||||
imap -> admin [label = "8080/tcp"; color="cyan"; fontcolor="cyan";];
|
||||
imap -> antispam [label = "11334/tcp"; color="cyan"; fontcolor="cyan";];
|
||||
imap -> proxy [label = "25/tcp"; color="cyan"; fontcolor="cyan";];
|
||||
imap -> fts_attachments [label = "9998/tcp"; color="cyan"; fontcolor="cyan";];
|
||||
|
||||
webmail -> front [label = "14190/tcp"; color="darkgoldenrod"; fontcolor="darkgoldenrod";];
|
||||
webmail -> front [label = "10025/tcp"; color="darkgoldenrod"; fontcolor="darkgoldenrod";];
|
||||
webmail -> front [label = "10143/tcp"; color="darkgoldenrod"; fontcolor="darkgoldenrod";];
|
||||
# carddav
|
||||
webmail -> proxy [label = "443/tcp"; color="darkgoldenrod"; fontcolor="darkgoldenrod";];
|
||||
|
||||
admin -> redis [label = "6379/tcp"; color="green"; fontcolor="green";];
|
||||
admin -> front [label = "2525/tcp"; color="green"; fontcolor="green";];
|
||||
|
||||
antispam -> redis [label = "6379/tcp"; color="magenta"; fontcolor="magenta";];
|
||||
antispam -> admin [label = "8080/tcp"; color="magenta"; fontcolor="magenta";];
|
||||
antispam -> oletools [label = "11343/tcp"; color="magenta"; fontcolor="magenta";];
|
||||
antispam -> antivirus [label = "3310/tcp"; color="magenta"; fontcolor="magenta";];
|
||||
|
||||
fetchmail -> admin [label = "8080/tcp"; color="chocolate"; fontcolor="chocolate";];
|
||||
fetchmail -> proxy [label = "25/tcp"; color="chocolate"; fontcolor="chocolate";];
|
||||
fetchmail -> front [label = "2525/tcp"; color="chocolate"; fontcolor="chocolate";];
|
||||
#
|
||||
# those don't need internet:
|
||||
# oletools
|
||||
# fts_attachments
|
||||
# redis
|
||||
}
|
||||
@@ -1,517 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/svg+xml": [
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
|
||||
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
|
||||
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
|
||||
"<!-- Generated by graphviz version 2.43.0 (0)\n",
|
||||
" -->\n",
|
||||
"<!-- Title: mailu Pages: 1 -->\n",
|
||||
"<svg width=\"706pt\" height=\"553pt\"\n",
|
||||
" viewBox=\"0.00 0.00 706.00 553.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
|
||||
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 549)\">\n",
|
||||
"<title>mailu</title>\n",
|
||||
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-549 702,-549 702,4 -4,4\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"349\" y=\"-7.8\" font-family=\"arial\" font-size=\"14.00\">Mailu</text>\n",
|
||||
"<!-- internet -->\n",
|
||||
"<g id=\"node1\" class=\"node\">\n",
|
||||
"<title>internet</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"297,-545 243,-545 243,-509 297,-509 297,-545\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"270\" y=\"-525.1\" font-family=\"arial\" font-size=\"8.00\">Internet</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front -->\n",
|
||||
"<g id=\"node2\" class=\"node\">\n",
|
||||
"<title>front</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"364,-464 310,-464 310,-428 364,-428 364,-464\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"337\" y=\"-444.1\" font-family=\"arial\" font-size=\"8.00\">Front</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge1\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.81,-521.99C180.56,-512.79 33,-491 33,-491 33,-491 33,-482 33,-482 33,-482 218.14,-460.68 299.46,-451.32\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.22,-454.76 309.75,-450.14 299.42,-447.8 300.22,-454.76\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"46.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">80/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge2\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.81,-520.91C190.94,-511.2 83,-491 83,-491 83,-491 83,-482 83,-482 83,-482 228.93,-461.89 299.56,-452.16\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.39,-455.58 309.82,-450.75 299.43,-448.64 300.39,-455.58\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"99\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">443/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge3\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.97,-518.83C204.35,-508.59 138,-491 138,-491 138,-491 138,-482 138,-482 138,-482 241.91,-463.72 299.56,-453.59\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.5,-456.97 309.75,-451.79 299.29,-450.08 300.5,-456.97\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"151.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge4\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.7,-514.35C218.84,-504.16 188,-491 188,-491 188,-491 188,-482 188,-482 188,-482 255.7,-466.1 299.83,-455.73\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.74,-459.11 309.67,-453.42 299.14,-452.3 300.74,-459.11\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"204\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">465/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge5\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M256.82,-508.91C249.96,-500.03 243,-491 243,-491 243,-491 243,-482 243,-482 243,-482 273.97,-470.47 300.36,-460.64\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"301.66,-463.89 309.81,-457.12 299.22,-457.33 301.66,-463.89\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"259\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">587/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge6\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M280.91,-508.86C288.77,-496.51 298,-482 298,-482 298,-482 303.42,-477.13 310.23,-471.03\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"312.85,-473.37 317.96,-464.09 308.18,-468.16 312.85,-473.37\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"314\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">110/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge7\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.25,-517.71C331.26,-507.36 385,-491 385,-491 385,-491 385,-482 385,-482 385,-482 377.6,-476.6 368.6,-470.04\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"370.58,-467.15 360.44,-464.09 366.46,-472.81 370.58,-467.15\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"401\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">995/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge8\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.34,-519.66C340.86,-509.58 421,-491 421,-491 421,-491 421,-482 421,-482 421,-482 396.19,-471.66 373.54,-462.22\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.62,-458.88 364.04,-458.27 371.93,-465.34 374.62,-458.88\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"437\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">143/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge9\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.19,-510.14C313.07,-500.88 330,-491 330,-491 330,-491 331.24,-483.18 332.68,-474.13\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"336.16,-474.56 334.27,-464.14 329.25,-473.46 336.16,-474.56\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"348\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">993/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge10\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.19,-520.91C349.06,-511.2 457,-491 457,-491 457,-491 457,-482 457,-482 457,-482 409.48,-468.14 374.25,-457.86\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.81,-454.38 364.23,-454.94 372.85,-461.1 374.81,-454.38\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"475.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">4190/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->front -->\n",
|
||||
"<g id=\"edge11\" class=\"edge\">\n",
|
||||
"<title>front->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.24,-449.75C374.02,-449.83 382,-448.58 382,-446 382,-444.43 379.04,-443.35 374.51,-442.77\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.4,-439.26 364.24,-442.25 374.05,-446.25 374.4,-439.26\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"400.5\" y=\"-444.1\" font-family=\"Times,serif\" font-size=\"8.00\">8008/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->front -->\n",
|
||||
"<g id=\"edge12\" class=\"edge\">\n",
|
||||
"<title>front->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.06,-452.31C389.18,-455.1 419,-452.99 419,-446 419,-439.94 396.57,-437.55 374.23,-438.84\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"373.73,-435.37 364.06,-439.69 374.31,-442.34 373.73,-435.37\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"437.5\" y=\"-444.1\" font-family=\"Times,serif\" font-size=\"8.00\">8000/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- admin -->\n",
|
||||
"<g id=\"node3\" class=\"node\">\n",
|
||||
"<title>admin</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"556,-302 502,-302 502,-266 556,-266 556,-302\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"529\" y=\"-282.1\" font-family=\"arial\" font-size=\"8.00\">Admin</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->admin -->\n",
|
||||
"<g id=\"edge13\" class=\"edge\">\n",
|
||||
"<title>front->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.05,-434.12C389.59,-423.84 424,-410 424,-410 424,-410 465,-347 465,-347 465,-347 485.85,-326.8 503.75,-309.46\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"506.43,-311.74 511.17,-302.27 501.55,-306.71 506.43,-311.74\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"483.5\" y=\"-363.1\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp -->\n",
|
||||
"<g id=\"node4\" class=\"node\">\n",
|
||||
"<title>smtp</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"565,-383 511,-383 511,-347 565,-347 565,-383\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"538\" y=\"-363.1\" font-family=\"arial\" font-size=\"8.00\">SMTP</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->smtp -->\n",
|
||||
"<g id=\"edge17\" class=\"edge\">\n",
|
||||
"<title>front->smtp</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.19,-439.53C413.12,-429.69 511,-410 511,-410 511,-410 516.33,-401.32 522.25,-391.67\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"525.24,-393.49 527.48,-383.14 519.27,-389.83 525.24,-393.49\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"529.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->smtp -->\n",
|
||||
"<g id=\"edge18\" class=\"edge\">\n",
|
||||
"<title>front->smtp</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.02,-440.5C420.74,-431.04 547,-410 547,-410 547,-410 547,-401 547,-401 547,-401 546.14,-397.65 544.95,-393.02\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"548.27,-391.9 542.39,-383.09 541.5,-393.65 548.27,-391.9\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"568.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">10025/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam -->\n",
|
||||
"<g id=\"node6\" class=\"node\">\n",
|
||||
"<title>antispam</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"632,-140 578,-140 578,-104 632,-104 632,-140\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"605\" y=\"-120.1\" font-family=\"arial\" font-size=\"8.00\">Antispam</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->antispam -->\n",
|
||||
"<g id=\"edge20\" class=\"edge\">\n",
|
||||
"<title>front->antispam</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.02,-441.32C430,-432.33 594,-410 594,-410 594,-410 649,-248 649,-248 649,-248 649,-239 649,-239 649,-239 620,-158 620,-158 620,-158 618.41,-154.3 616.27,-149.3\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"619.48,-147.9 612.32,-140.09 613.05,-150.66 619.48,-147.9\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"663.5\" y=\"-282.1\" font-family=\"Times,serif\" font-size=\"8.00\">11334/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap -->\n",
|
||||
"<g id=\"node8\" class=\"node\">\n",
|
||||
"<title>imap</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"411,-221 357,-221 357,-185 411,-185 411,-221\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"384\" y=\"-201.1\" font-family=\"arial\" font-size=\"8.00\">IMAP</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->imap -->\n",
|
||||
"<g id=\"edge14\" class=\"edge\">\n",
|
||||
"<title>front->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M338.92,-427.88C342.75,-393.65 351,-320 351,-320 351,-320 370,-239 370,-239 370,-239 371.41,-235.47 373.34,-230.66\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"376.7,-231.67 377.16,-221.09 370.2,-229.07 376.7,-231.67\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"369.5\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">4190/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->imap -->\n",
|
||||
"<g id=\"edge15\" class=\"edge\">\n",
|
||||
"<title>front->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M309.86,-441.8C236.54,-433.16 40,-410 40,-410 40,-410 0,-383 0,-383 0,-383 0,-347 0,-347 0,-347 286,-239 286,-239 286,-239 319.57,-227.01 347.42,-217.07\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"348.72,-220.32 356.96,-213.66 346.37,-213.72 348.72,-220.32\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"87\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">143/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->imap -->\n",
|
||||
"<g id=\"edge16\" class=\"edge\">\n",
|
||||
"<title>front->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M346.28,-427.91C351.1,-419.03 356,-410 356,-410 356,-410 388,-329 388,-329 388,-329 388,-320 388,-320 388,-320 386.13,-265.76 384.95,-231.46\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"388.43,-230.99 384.59,-221.12 381.44,-231.23 388.43,-230.99\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"404\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">110/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webdav -->\n",
|
||||
"<g id=\"node9\" class=\"node\">\n",
|
||||
"<title>webdav</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"63,-383 9,-383 9,-347 63,-347 63,-383\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"36\" y=\"-363.1\" font-family=\"arial\" font-size=\"8.00\">WebDAV</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->webdav -->\n",
|
||||
"<g id=\"edge21\" class=\"edge\">\n",
|
||||
"<title>front->webdav</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M309.79,-441.74C237.3,-433.05 45,-410 45,-410 45,-410 43.4,-402.18 41.55,-393.13\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"44.94,-392.23 39.51,-383.14 38.08,-393.64 44.94,-392.23\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"63.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">5232/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail -->\n",
|
||||
"<g id=\"node10\" class=\"node\">\n",
|
||||
"<title>webmail</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"211,-383 157,-383 157,-347 211,-347 211,-383\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"184\" y=\"-363.1\" font-family=\"arial\" font-size=\"8.00\">Webmail</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->webmail -->\n",
|
||||
"<g id=\"edge19\" class=\"edge\">\n",
|
||||
"<title>front->webmail</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M310,-437.06C274.89,-426.73 218,-410 218,-410 218,-410 211.02,-400.97 203.39,-391.09\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"206.13,-388.91 197.24,-383.14 200.59,-393.19 206.13,-388.91\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"231.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">80/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- redis -->\n",
|
||||
"<g id=\"node5\" class=\"node\">\n",
|
||||
"<title>redis</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"537,-59 483,-59 483,-23 537,-23 537,-59\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"510\" y=\"-39.1\" font-family=\"arial\" font-size=\"8.00\">Redis</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- admin->redis -->\n",
|
||||
"<g id=\"edge32\" class=\"edge\">\n",
|
||||
"<title>admin->redis</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M522.36,-265.88C509.84,-233.62 484,-167 484,-167 484,-167 484,-158 484,-158 484,-158 496.26,-103.29 503.95,-69.01\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"507.39,-69.64 506.16,-59.12 500.56,-68.11 507.39,-69.64\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"502.5\" y=\"-160.6\" font-family=\"Times,serif\" font-size=\"8.00\">6379/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- admin->imap -->\n",
|
||||
"<g id=\"edge33\" class=\"edge\">\n",
|
||||
"<title>admin->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M501.75,-274.71C467.74,-264.36 414,-248 414,-248 414,-248 408.08,-239.32 401.5,-229.67\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"404.21,-227.43 395.68,-221.14 398.43,-231.37 404.21,-227.43\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"432.5\" y=\"-241.6\" font-family=\"Times,serif\" font-size=\"8.00\">2525/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp->front -->\n",
|
||||
"<g id=\"edge23\" class=\"edge\">\n",
|
||||
"<title>smtp->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M510.95,-379.68C485.41,-392.6 451,-410 451,-410 451,-410 407.25,-423.43 373.95,-433.65\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"372.55,-430.42 364.02,-436.71 374.6,-437.12 372.55,-430.42\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"485.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">2525/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp->admin -->\n",
|
||||
"<g id=\"edge22\" class=\"edge\">\n",
|
||||
"<title>smtp->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M536.05,-346.86C534.89,-336.71 533.4,-323.63 532.09,-312.12\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"535.56,-311.65 530.95,-302.11 528.6,-312.44 535.56,-311.65\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"551.5\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp->antispam -->\n",
|
||||
"<g id=\"edge24\" class=\"edge\">\n",
|
||||
"<title>smtp->antispam</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M555.58,-346.91C564.72,-338.03 574,-329 574,-329 574,-329 608,-221 608,-221 608,-221 608,-185 608,-185 608,-185 607.12,-166.91 606.32,-150.27\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"609.81,-150.09 605.84,-140.27 602.82,-150.43 609.81,-150.09\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"623.5\" y=\"-241.6\" font-family=\"Times,serif\" font-size=\"8.00\">11332/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->admin -->\n",
|
||||
"<g id=\"edge35\" class=\"edge\">\n",
|
||||
"<title>antispam->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M597.99,-140.14C592.94,-152.49 587,-167 587,-167 587,-167 559.17,-222.66 542.07,-256.87\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"538.9,-255.37 537.56,-265.88 545.16,-258.5 538.9,-255.37\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"590.5\" y=\"-201.1\" font-family=\"Times,serif\" font-size=\"8.00\">80/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->redis -->\n",
|
||||
"<g id=\"edge34\" class=\"edge\">\n",
|
||||
"<title>antispam->redis</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M583.03,-103.91C571.6,-95.03 560,-86 560,-86 560,-86 548.91,-76.24 537.17,-65.91\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"539.29,-63.12 529.47,-59.14 534.67,-68.37 539.29,-63.12\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"578.5\" y=\"-79.6\" font-family=\"Times,serif\" font-size=\"8.00\">6379/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antivirus -->\n",
|
||||
"<g id=\"node7\" class=\"node\">\n",
|
||||
"<title>antivirus</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"698,-59 644,-59 644,-23 698,-23 698,-59\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"671\" y=\"-39.1\" font-family=\"arial\" font-size=\"8.00\">Anti-Virus</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->antivirus -->\n",
|
||||
"<g id=\"edge37\" class=\"edge\">\n",
|
||||
"<title>antispam->antivirus</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M626.48,-103.91C637.65,-95.03 649,-86 649,-86 649,-86 653.17,-77.67 657.87,-68.26\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"661.09,-69.65 662.43,-59.14 654.83,-66.52 661.09,-69.65\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"671.5\" y=\"-79.6\" font-family=\"Times,serif\" font-size=\"8.00\">3310/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- oletools -->\n",
|
||||
"<g id=\"node12\" class=\"node\">\n",
|
||||
"<title>oletools</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"626,-59 572,-59 572,-23 626,-23 626,-59\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"599\" y=\"-39.1\" font-family=\"arial\" font-size=\"8.00\">Oletools</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->oletools -->\n",
|
||||
"<g id=\"edge36\" class=\"edge\">\n",
|
||||
"<title>antispam->oletools</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M603.7,-103.86C602.93,-93.71 601.93,-80.63 601.06,-69.12\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"604.55,-68.81 600.3,-59.11 597.57,-69.34 604.55,-68.81\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"623.5\" y=\"-79.6\" font-family=\"Times,serif\" font-size=\"8.00\">11343/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->front -->\n",
|
||||
"<g id=\"edge27\" class=\"edge\">\n",
|
||||
"<title>imap->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M374.23,-221.09C369.16,-229.97 364,-239 364,-239 364,-239 320,-320 320,-320 320,-320 320,-329 320,-329 320,-329 328.02,-383.71 333.04,-417.99\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"329.58,-418.5 334.49,-427.88 336.5,-417.48 329.58,-418.5\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"333.5\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->admin -->\n",
|
||||
"<g id=\"edge25\" class=\"edge\">\n",
|
||||
"<title>imap->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M411.25,-217.43C431.27,-227.3 455,-239 455,-239 455,-239 474.19,-250.41 493.02,-261.61\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"491.48,-264.76 501.87,-266.87 495.06,-258.75 491.48,-264.76\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"487.5\" y=\"-241.6\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->antispam -->\n",
|
||||
"<g id=\"edge26\" class=\"edge\">\n",
|
||||
"<title>imap->antispam</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M411.19,-195.25C452.16,-185.08 525,-167 525,-167 525,-167 547.74,-154.49 568.98,-142.81\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"570.84,-145.78 577.92,-137.9 567.47,-139.65 570.84,-145.78\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"561.5\" y=\"-160.6\" font-family=\"Times,serif\" font-size=\"8.00\">11334/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fts_attachments -->\n",
|
||||
"<g id=\"node13\" class=\"node\">\n",
|
||||
"<title>fts_attachments</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"411,-140 357,-140 357,-104 411,-104 411,-140\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"384\" y=\"-120.1\" font-family=\"arial\" font-size=\"8.00\">Tika</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->fts_attachments -->\n",
|
||||
"<g id=\"edge28\" class=\"edge\">\n",
|
||||
"<title>imap->fts_attachments</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M384,-184.86C384,-174.71 384,-161.63 384,-150.12\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"387.5,-150.11 384,-140.11 380.5,-150.11 387.5,-150.11\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"402.5\" y=\"-160.6\" font-family=\"Times,serif\" font-size=\"8.00\">9998/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail->front -->\n",
|
||||
"<g id=\"edge29\" class=\"edge\">\n",
|
||||
"<title>webmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M211.04,-380.16C235.6,-393.03 268,-410 268,-410 268,-410 283.98,-418.1 300.56,-426.51\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"299.23,-429.76 309.73,-431.17 302.39,-423.52 299.23,-429.76\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"289.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">14190/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail->front -->\n",
|
||||
"<g id=\"edge30\" class=\"edge\">\n",
|
||||
"<title>webmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M156.96,-375.66C127.95,-386.02 86,-401 86,-401 86,-401 86,-410 86,-410 86,-410 229.5,-430.01 299.49,-439.77\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"299.27,-443.27 309.66,-441.19 300.24,-436.34 299.27,-443.27\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"107.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">10025/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail->front -->\n",
|
||||
"<g id=\"edge31\" class=\"edge\">\n",
|
||||
"<title>webmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M174.72,-383.09C169.9,-391.97 165,-401 165,-401 165,-401 165,-410 165,-410 165,-410 249.24,-427.14 299.81,-437.43\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"299.25,-440.89 309.75,-439.45 300.64,-434.03 299.25,-440.89\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"186.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">10143/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail -->\n",
|
||||
"<g id=\"node11\" class=\"node\">\n",
|
||||
"<title>fetchmail</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"654,-545 600,-545 600,-509 654,-509 654,-545\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"627\" y=\"-525.1\" font-family=\"arial\" font-size=\"8.00\">Fetchmail</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail->front -->\n",
|
||||
"<g id=\"edge39\" class=\"edge\">\n",
|
||||
"<title>fetchmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M599.96,-516.78C562.13,-503.87 498,-482 498,-482 498,-482 421.83,-465.44 374.28,-455.1\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.83,-451.64 364.31,-452.94 373.34,-458.48 374.83,-451.64\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"535.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail->front -->\n",
|
||||
"<g id=\"edge40\" class=\"edge\">\n",
|
||||
"<title>fetchmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M599.87,-509.87C578.75,-497.31 553,-482 553,-482 553,-482 436.36,-463.1 374.51,-453.08\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.79,-449.58 364.36,-451.43 373.67,-456.49 374.79,-449.58\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"585.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">2525/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail->admin -->\n",
|
||||
"<g id=\"edge38\" class=\"edge\">\n",
|
||||
"<title>fetchmail->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M622.57,-508.79C609.83,-459.28 574,-320 574,-320 574,-320 567.2,-314.71 558.88,-308.24\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"561.02,-305.47 550.97,-302.09 556.72,-310.99 561.02,-305.47\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"614.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"</g>\n",
|
||||
"</svg>\n"
|
||||
],
|
||||
"text/plain": [
|
||||
"<graphviz.sources.Source at 0x7f2c4e69e690>"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import graphviz\n",
|
||||
"\n",
|
||||
"a = \"\"\"\n",
|
||||
"digraph mailu {\n",
|
||||
" label = \"Mailu\";\n",
|
||||
" fontname = \"arial\";\n",
|
||||
" \n",
|
||||
" node [shape = box; fontname = \"arial\"; fontsize = 8; style = filled; color = \"#d3edea\";];\n",
|
||||
" splines = \"compound\";\n",
|
||||
" // node [shape = \"box\"; fontsize = \"10\";];\n",
|
||||
" edge [fontsize = \"8\";];\n",
|
||||
" \n",
|
||||
" # Components\n",
|
||||
" internet [label = \"Internet\";];\n",
|
||||
" front [label = \"Front\";];\n",
|
||||
" admin [label = \"Admin\";];\n",
|
||||
" smtp [label = \"SMTP\";];\n",
|
||||
" redis [label = \"Redis\";];\n",
|
||||
" antispam [label = \"Antispam\";];\n",
|
||||
" antivirus [label = \"Anti-Virus\";];\n",
|
||||
" imap [label = \"IMAP\";];\n",
|
||||
" webdav [label = \"WebDAV\";];\n",
|
||||
" webmail [label = \"Webmail\";];\n",
|
||||
" fetchmail [label = \"Fetchmail\";];\n",
|
||||
" oletools [label = \"Oletools\"];\n",
|
||||
" fts_attachments [label = \"Tika\"];\n",
|
||||
" \n",
|
||||
" # Front from internet\n",
|
||||
" internet -> front [label = \"80/tcp\";];\n",
|
||||
" internet -> front [label = \"443/tcp\";];\n",
|
||||
" internet -> front [label = \"25/tcp\";];\n",
|
||||
" internet -> front [label = \"465/tcp\";];\n",
|
||||
" internet -> front [label = \"587/tcp\";];\n",
|
||||
" internet -> front [label = \"110/tcp\";];\n",
|
||||
" internet -> front [label = \"995/tcp\";];\n",
|
||||
" internet -> front [label = \"143/tcp\";];\n",
|
||||
" internet -> front [label = \"993/tcp\";];\n",
|
||||
" internet -> front [label = \"4190/tcp\";];\n",
|
||||
" \n",
|
||||
" front -> front [label = \"8008/tcp\";];\n",
|
||||
" front -> front [label = \"8000/tcp\";];\n",
|
||||
" front -> admin [label = \"8080/tcp\";];\n",
|
||||
" front -> imap [label = \"4190/tcp\";];\n",
|
||||
" front -> imap [label = \"143/tcp\";];\n",
|
||||
" front -> imap [label = \"110/tcp\";];\n",
|
||||
" front -> smtp [label = \"25/tcp\";];\n",
|
||||
" front -> smtp [label = \"10025/tcp\";];\n",
|
||||
" front -> webmail [label = \"80/tcp\";];\n",
|
||||
" front -> antispam [label = \"11334/tcp\";];\n",
|
||||
" front -> webdav [label = \"5232/tcp\";];\n",
|
||||
" \n",
|
||||
" smtp -> admin [label = \"8080/tcp\";];\n",
|
||||
" smtp -> front [label = \"2525/tcp\";];\n",
|
||||
" smtp -> antispam [label = \"11332/tcp\";];\n",
|
||||
" \n",
|
||||
" imap -> admin [label = \"8080/tcp\";];\n",
|
||||
" imap -> antispam [label = \"11334/tcp\";];\n",
|
||||
" imap -> front [label = \"25/tcp\";];\n",
|
||||
" imap -> fts_attachments [label = \"9998/tcp\";];\n",
|
||||
" \n",
|
||||
" webmail -> front [label = \"14190/tcp\";];\n",
|
||||
" webmail -> front [label = \"10025/tcp\";];\n",
|
||||
" webmail -> front [label = \"10143/tcp\";];\n",
|
||||
" \n",
|
||||
" admin -> redis [label = \"6379/tcp\";];\n",
|
||||
" admin -> imap [label = \"2525/tcp\";];\n",
|
||||
" \n",
|
||||
" antispam -> redis [label = \"6379/tcp\";];\n",
|
||||
" antispam -> admin [label = \"80/tcp\";];\n",
|
||||
" antispam -> oletools [label = \"11343/tcp\";];\n",
|
||||
" antispam -> antivirus [label = \"3310/tcp\";];\n",
|
||||
" \n",
|
||||
" fetchmail -> admin [label = \"8080/tcp\"]\n",
|
||||
" fetchmail -> front [label = \"25/tcp\"]\n",
|
||||
" fetchmail -> front [label = \"2525/tcp\"]\n",
|
||||
" #\n",
|
||||
" # those don't need internet:\n",
|
||||
" # oletools\n",
|
||||
" # fts_attachments\n",
|
||||
" # redis\n",
|
||||
"}\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"dot = graphviz.Source(a)\n",
|
||||
"dot\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -46,7 +46,8 @@ In the case of *certbot* you could write a script to be executed as `deploy hook
|
||||
#!/bin/sh
|
||||
cp /etc/letsencrypt/live/domain.com/privkey.pem /mailu/certs/key.pem || exit 1
|
||||
cp /etc/letsencrypt/live/domain.com/fullchain.pem /mailu/certs/cert.pem || exit 1
|
||||
docker exec mailu_front_1 nginx -s reload
|
||||
docker exec mailu-front-1 nginx -s reload
|
||||
docker exec mailu-front-1 doveadm reload
|
||||
|
||||
And the certbot command you will use in crontab would look something like:
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import binascii
|
||||
import time
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -32,6 +33,19 @@ poll "{host}" proto {protocol} port {port}
|
||||
{lmtp}
|
||||
"""
|
||||
|
||||
def imaputf7encode(s):
|
||||
"""Encode a string into RFC2060 aka IMAP UTF7"""
|
||||
out = ''
|
||||
enc = ''
|
||||
for c in s.replace('&','&-') + 'X':
|
||||
if '\x20' <= c <= '\x7f':
|
||||
if enc:
|
||||
out += f'&{binascii.b2a_base64(enc.encode("utf-16-be")).rstrip(b"\n=").replace(b"/", b",").decode("ascii")}-'
|
||||
enc = ''
|
||||
out += c
|
||||
else:
|
||||
enc += c
|
||||
return out[:-1]
|
||||
|
||||
def escape_rc_string(arg):
|
||||
return "".join("\\x%2x" % ord(char) for char in arg)
|
||||
@@ -54,13 +68,13 @@ def run(debug):
|
||||
options = "options antispam 501, 504, 550, 553, 554"
|
||||
options += " ssl" if fetch["tls"] else ""
|
||||
options += " keep" if fetch["keep"] else " fetchall"
|
||||
folders = "folders %s" % ((','.join('"' + item + '"' for item in fetch['folders'])) if fetch['folders'] else '"INBOX"')
|
||||
folders = f"folders {",".join(f'"{imaputf7encode(item).replace('"',r"\34")}"' for item in fetch["folders"]) or '"INBOX"'}"
|
||||
fetchmailrc += RC_LINE.format(
|
||||
user_email=escape_rc_string(fetch["user_email"]),
|
||||
protocol=fetch["protocol"],
|
||||
host=escape_rc_string(fetch["host"]),
|
||||
port=fetch["port"],
|
||||
smtphost=f'{os.environ["FRONT_ADDRESS"]}' if fetch['scan'] else f'{os.environ["FRONT_ADDRESS"]}/2525',
|
||||
smtphost=f'{os.environ["HOSTNAMES"].split(",")[0]}' if fetch['scan'] and os.environ.get('PROXY_PROTOCOL_25', False) else f'{os.environ["FRONT_ADDRESS"]}' if fetch['scan'] else f'{os.environ["FRONT_ADDRESS"]}/2525',
|
||||
username=escape_rc_string(fetch["username"]),
|
||||
password=escape_rc_string(fetch["password"]),
|
||||
options=options,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[server]
|
||||
ssl = False
|
||||
hosts = 0.0.0.0:5232
|
||||
hosts = 0.0.0.0:5232, [::]:5232
|
||||
|
||||
[encoding]
|
||||
request = utf-8
|
||||
|
||||
@@ -15,7 +15,7 @@ COPY main.py ./main.py
|
||||
RUN echo $VERSION >> /version
|
||||
|
||||
EXPOSE 80/tcp
|
||||
HEALTHCHECK --start-period=350s CMD curl -skfLo /dev/null http://localhost/
|
||||
HEALTHCHECK --start-period=350s CMD curl -m3 -skfLo /dev/null http://localhost/
|
||||
USER mailu
|
||||
|
||||
CMD gunicorn -w 4 -b :80 --access-logfile - --error-logfile - --preload main:app
|
||||
|
||||
@@ -217,7 +217,7 @@ services:
|
||||
# Optional services
|
||||
{% if antivirus_enabled %}
|
||||
antivirus:
|
||||
image: clamav/clamav-debian:1.2.0-6
|
||||
image: clamav/clamav-debian:1.2.3-45
|
||||
restart: always
|
||||
logging:
|
||||
driver: journald
|
||||
|
||||
@@ -8,5 +8,5 @@ docker compose -f tests/compose/core/docker-compose.yml exec -T admin flask mail
|
||||
docker compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu admin admin mailu.io 'password' --mode=update || exit 1
|
||||
docker compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user user mailu.io 'password' || exit 1
|
||||
docker compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user 'user/with/slash' mailu.io 'password' || exit 1
|
||||
docker compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user 'user_UTF8' mailu.io 'password€' || exit 1
|
||||
docker compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user 'user_UTF8' mailu.io 'pa…ss%e9word€' || exit 1
|
||||
echo "User testing successful!"
|
||||
|
||||
@@ -8,7 +8,7 @@ import managesieve
|
||||
|
||||
SERVER='localhost'
|
||||
USERNAME='user_UTF8@mailu.io'
|
||||
PASSWORD='password€'
|
||||
PASSWORD='pa…ss%e9word€'
|
||||
#https://github.com/python/cpython/issues/73936
|
||||
#SMTPlib does not support UTF8 passwords.
|
||||
USERNAME_ASCII='user@mailu.io'
|
||||
@@ -139,4 +139,4 @@ if __name__ == '__main__':
|
||||
test_SMTP(SERVER, USERNAME_ASCII, PASSWORD_ASCII)
|
||||
test_managesieve(SERVER, USERNAME, PASSWORD)
|
||||
#https://github.com/python/cpython/issues/73936
|
||||
#SMTPlib does not support UTF8 passwords.
|
||||
#SMTPlib does not support UTF8 passwords.
|
||||
|
||||
1
towncrier/newsfragments/2296.bugfix
Normal file
1
towncrier/newsfragments/2296.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Ensure fetchmail can deal with special characters in folder names
|
||||
1
towncrier/newsfragments/3272.bugfix
Normal file
1
towncrier/newsfragments/3272.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Increase the size of buffers for webmail
|
||||
5
towncrier/newsfragments/3347.bugfix
Normal file
5
towncrier/newsfragments/3347.bugfix
Normal file
@@ -0,0 +1,5 @@
|
||||
Update to a newer clamav 1.2.3-45
|
||||
Update to snappymail 2.36.4
|
||||
Update to roundcube 1.6.8 (CVE-2024-42009, CVE-2024-42008, CVE-2024-42010)
|
||||
Add a new DNS entry for autodiscover (old MUA autoconfiguration)
|
||||
Clarify the language in the documentation related to rspamd overrides
|
||||
1
towncrier/newsfragments/3349.bugfix
Normal file
1
towncrier/newsfragments/3349.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix email-forwarding when set from the web interface
|
||||
1
towncrier/newsfragments/3364.bugfix
Normal file
1
towncrier/newsfragments/3364.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix a bug preventing percent characters from being used in passwords
|
||||
1
towncrier/newsfragments/3379.bugfix
Normal file
1
towncrier/newsfragments/3379.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix #3379: DEFAULT_QUOTA
|
||||
1
towncrier/newsfragments/3384.bugfix
Normal file
1
towncrier/newsfragments/3384.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Ensure that file:// protocol is not allowed in CURL
|
||||
1
towncrier/newsfragments/3389.misc
Normal file
1
towncrier/newsfragments/3389.misc
Normal file
@@ -0,0 +1 @@
|
||||
Update roundcube to 1.6.9
|
||||
2
towncrier/newsfragments/3398.misc
Normal file
2
towncrier/newsfragments/3398.misc
Normal file
@@ -0,0 +1,2 @@
|
||||
Disable HARDENED_MALLOC unless the requirements are met
|
||||
Ensure the healthchecks timeout
|
||||
1
towncrier/newsfragments/3401.bugfix
Normal file
1
towncrier/newsfragments/3401.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix an error that can occur when using snappymail
|
||||
1
towncrier/newsfragments/3402.bugfix
Normal file
1
towncrier/newsfragments/3402.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix a potential problem with SO_REUSEADDR that may prevent admin from starting up
|
||||
1
towncrier/newsfragments/3403.bugfix
Normal file
1
towncrier/newsfragments/3403.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
fix INBOUND_TLS_ENFORCE
|
||||
1
towncrier/newsfragments/3405.bugfix
Normal file
1
towncrier/newsfragments/3405.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Update the documentation: ensure that users reload dovecot too if they manually configure certificates
|
||||
1
towncrier/newsfragments/3411.bugfix
Normal file
1
towncrier/newsfragments/3411.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Ensure we do not nuke all web-sessions when a password is changed using the command line
|
||||
1
towncrier/newsfragments/3420.bugfix
Normal file
1
towncrier/newsfragments/3420.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
The reload functionality of nginx/dovecot upon change of the certificates failed with an error.
|
||||
2
towncrier/newsfragments/3450.bugfix
Normal file
2
towncrier/newsfragments/3450.bugfix
Normal file
@@ -0,0 +1,2 @@
|
||||
Ensure we can do more than 100 parallel sessions.
|
||||
Allow dovecot's config to be overriden in front too
|
||||
1
towncrier/newsfragments/3467.bugfix
Normal file
1
towncrier/newsfragments/3467.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix broken overrides in 2024.06.17
|
||||
1
towncrier/newsfragments/3531.bugfix
Normal file
1
towncrier/newsfragments/3531.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Ensure we have both RSA and ECDSA certs when using letsencrypt
|
||||
1
towncrier/newsfragments/3613.bugfix
Normal file
1
towncrier/newsfragments/3613.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
HTTP/2 does not require ipv6; in fact it does not require SSL certs either
|
||||
1
towncrier/newsfragments/3618.bugfix
Normal file
1
towncrier/newsfragments/3618.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Filter logs line based and in binary mode without decoding utf-8
|
||||
1
towncrier/newsfragments/3622.misc
Normal file
1
towncrier/newsfragments/3622.misc
Normal file
@@ -0,0 +1 @@
|
||||
Upgrade to alpine 3.20.3
|
||||
1
towncrier/newsfragments/3648.bugfix
Normal file
1
towncrier/newsfragments/3648.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Upgrade snappymail to v2.38.2 ; this is a security fix for GHSA-2rq7-79vp-ffxm (mXSS)
|
||||
1
towncrier/newsfragments/3650.bugfix
Normal file
1
towncrier/newsfragments/3650.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Don't check empty passwords against HIBP
|
||||
@@ -28,7 +28,7 @@ RUN set -euxo pipefail \
|
||||
; mkdir -p /run/nginx /conf
|
||||
|
||||
# roundcube
|
||||
ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.7/roundcubemail-1.6.7-complete.tar.gz
|
||||
ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.9/roundcubemail-1.6.9-complete.tar.gz
|
||||
ENV CARDDAV_URL https://github.com/mstilkerich/rcmcarddav/releases/download/v5.1.0/carddav-v5.1.0.tar.gz
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
@@ -54,7 +54,7 @@ COPY roundcube/config/config.inc.carddav.php /var/www/roundcube/plugins/carddav/
|
||||
|
||||
# snappymail
|
||||
|
||||
ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v2.36.1/snappymail-2.36.1.tar.gz
|
||||
ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v2.38.2/snappymail-2.38.2.tar.gz
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; mkdir /var/www/snappymail \
|
||||
@@ -94,6 +94,6 @@ VOLUME /overrides
|
||||
|
||||
CMD /start.py
|
||||
|
||||
HEALTHCHECK CMD curl -f -L http://localhost/ping || exit 1
|
||||
HEALTHCHECK CMD curl -m3 -f -L http://localhost/ping || exit 1
|
||||
|
||||
RUN echo $VERSION >> /version
|
||||
|
||||
@@ -55,6 +55,16 @@ server {
|
||||
{% else %}
|
||||
fastcgi_param SCRIPT_NAME {{WEB_WEBMAIL}}/$fastcgi_script_name;
|
||||
{% endif %}
|
||||
|
||||
# fastcgi buffers for php-fpm #
|
||||
fastcgi_buffers 16 32k;
|
||||
fastcgi_buffer_size 64k;
|
||||
fastcgi_busy_buffers_size 64k;
|
||||
|
||||
# nginx buffers #
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 256k;
|
||||
proxy_busy_buffers_size 256k;
|
||||
}
|
||||
|
||||
location ~ (^|/)\. {
|
||||
|
||||
@@ -39,7 +39,9 @@ sp.disable_function.function("chmod").param("permissions").value("438").drop();
|
||||
sp.disable_function.function("chmod").param("permissions").value("511").drop();
|
||||
|
||||
# Prevent various `mail`-related vulnerabilities
|
||||
# Uncommend the second rule if you're using php8.3+
|
||||
sp.disable_function.function("mail").param("additional_parameters").value_r("\\-").drop();
|
||||
sp.disable_function.function("mail").param("additional_params").value_r("\\-").drop();
|
||||
|
||||
# Since it's now burned, me might as well mitigate it publicly
|
||||
sp.disable_function.function("putenv").param("assignment").value_r("LD_").drop()
|
||||
@@ -52,8 +54,7 @@ sp.disable_function.function("putenv").param("assignment").value_r("GCONV_").dro
|
||||
sp.disable_function.function("extract").param("array").value_r("^_").drop()
|
||||
sp.disable_function.function("extract").param("flags").value("0").drop()
|
||||
|
||||
# This is also burned:
|
||||
# ini_set('open_basedir','..');chdir('..');…;chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/etc/passwd'));
|
||||
# See https://dustri.org/b/ini_set-based-open_basedir-bypass.html
|
||||
# Since we have no way of matching on two parameters at the same time, we're
|
||||
# blocking calls to open_basedir altogether: nobody is using it via ini_set anyway.
|
||||
# Moreover, there are non-public bypasses that are also using this vector ;)
|
||||
@@ -119,12 +120,17 @@ sp.disable_function.function("curl_setopt").param("value").value("2").allow();
|
||||
sp.disable_function.function("curl_setopt").param("option").value("64").drop().alias("Please don't turn CURLOPT_SSL_VERIFYCLIENT off.");
|
||||
sp.disable_function.function("curl_setopt").param("option").value("81").drop().alias("Please don't turn CURLOPT_SSL_VERIFYHOST off.");
|
||||
|
||||
# Ensure that file:// protocol is not allowed in CURL
|
||||
sp.disable_function.function("curl_setopt").param("value").value_r("file://").drop().alias("file:// protocol is disabled");
|
||||
sp.disable_function.function("curl_init").param("url").value_r("file://").drop().alias("file:// protocol is disabled");
|
||||
|
||||
# File upload
|
||||
sp.disable_function.function("move_uploaded_file").param("to").value_r("\\.ph").drop();
|
||||
sp.disable_function.function("move_uploaded_file").param("to").value_r("\\.ht").drop();
|
||||
|
||||
# Logging lockdown
|
||||
sp.disable_function.function("ini_set").param("option").value_r("error_log").drop()
|
||||
sp.disable_function.function("ini_set").param("option").value_r("display_errors").filename_r("/var/www/snappymail/snappymail/v/[0-9]+\.[0-9]+\.[0-9]+/app/libraries/snappymail/shutdown.php").allow();
|
||||
sp.disable_function.function("ini_set").param("option").value_r("display_errors").drop()
|
||||
|
||||
sp.auto_cookie_secure.enable();
|
||||
|
||||
Reference in New Issue
Block a user