1
0
mirror of https://github.com/Mailu/Mailu.git synced 2024-12-04 10:24:41 +02:00

Implement managesieve support

This commit is contained in:
Florent Daigniere 2023-04-20 15:36:17 +02:00
parent 152a2bf175
commit 107b0ab5ff
15 changed files with 134 additions and 38 deletions

View File

@ -20,7 +20,7 @@ Main features include:
- **Standard email server**, IMAP and IMAP+, SMTP and Submission with autoconfiguration profiles for clients
- **Advanced email features**, aliases, domain aliases, custom routing
- **Web access**, multiple Webmails and administration interface
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
- **User features**, aliases, auto-reply, auto-forward, fetched accounts, managesieve
- **Admin features**, global admins, announcements, per-domain delegation, quotas
- **Security**, enforced TLS, DANE, MTA-STS, Letsencrypt!, outgoing DKIM, anti-virus scanner, [Snuffleupagus](https://github.com/jvoisin/snuffleupagus/), block malicious attachments
- **Antispam**, auto-learn, greylisting, DMARC and SPF, anti-spoofing

View File

@ -13,7 +13,8 @@ STATUSES = {
"authentication": ("Authentication credentials invalid", {
"imap": "AUTHENTICATIONFAILED",
"smtp": "535 5.7.8",
"pop3": "-ERR Authentication failed"
"pop3": "-ERR Authentication failed",
"sieve": "AuthFailed"
}),
"encryption": ("Must issue a STARTTLS command first", {
"smtp": "530 5.7.0"
@ -32,7 +33,7 @@ def check_credentials(user, password, ip, protocol=None, auth_port=None):
return False
is_ok = False
# webmails
if auth_port in WEBMAIL_PORTS and password.startswith('token-'):
if auth_port in WEBMAIL_PORTS or auth_port == '4190' and password.startswith('token-'):
if utils.verify_temp_token(user.get_id(), password):
is_ok = True
# All tokens are 32 characters hex lowercase
@ -50,8 +51,8 @@ def handle_authentication(headers):
""" Handle an HTTP nginx authentication request
See: http://nginx.org/en/docs/mail/ngx_mail_auth_http_module.html#protocol
"""
method = headers["Auth-Method"]
protocol = headers["Auth-Protocol"]
method = headers["Auth-Method"].lower()
protocol = headers["Auth-Protocol"].lower()
# Incoming mail, no authentication
if method == "none" and protocol == "smtp":
server, port = get_server(protocol, False)
@ -121,7 +122,7 @@ def handle_authentication(headers):
"Auth-Wait": 0
}
# Unexpected
return {}
raise Exception("SHOULD NOT HAPPEN")
def get_status(protocol, status):
@ -140,6 +141,8 @@ def get_server(protocol, authenticated=False):
hostname, port = app.config['SMTP_ADDRESS'], 10025
else:
hostname, port = app.config['SMTP_ADDRESS'], 25
elif protocol == "sieve":
hostname, port = app.config['IMAP_ADDRESS'], 4190
try:
# test if hostname is already resolved to an ip address
ipaddress.ip_address(hostname)

View File

@ -12,6 +12,7 @@ default_login_user = mail
default_internal_group = dovecot
haproxy_trusted_networks = {{ SUBNET }} {{ SUBNET6 }}
login_trusted_networks = {{ SUBNET }} {{ SUBNET6 }}
###############
# Mailboxes
@ -149,7 +150,6 @@ service lmtp {
service managesieve-login {
inet_listener sieve {
port = 4190
haproxy = yes
}
}

View File

@ -17,17 +17,17 @@ ARG VERSION
LABEL version=$VERSION
RUN set -euxo pipefail \
; apk add --no-cache certbot nginx nginx-mod-http-brotli nginx-mod-stream nginx-mod-mail openssl \
; rm /etc/nginx/conf.d/stream.conf
; apk add --no-cache certbot nginx nginx-mod-http-brotli nginx-mod-mail openssl dovecot-lua dovecot-pigeonhole-plugin
COPY conf/ /conf/
COPY --from=static /static/ /static/
COPY *.py /
COPY proxy.conf login.lua /
RUN echo $VERSION >/version
EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp
# EXPOSE 10025/tcp 10143/tcp 14190/tcp
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
HEALTHCHECK --start-period=60s CMD curl -skfLo /dev/null http://127.0.0.1:10204/health
VOLUME ["/certs", "/overrides"]

View File

@ -5,7 +5,6 @@ pcre_jit on;
error_log /dev/stderr notice;
pid /var/run/nginx.pid;
load_module "modules/ngx_mail_module.so";
load_module "modules/ngx_stream_module.so";
events {
worker_connections 1024;
@ -302,25 +301,6 @@ http {
include /etc/nginx/conf.d/*.conf;
}
stream {
log_format main '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time "$upstream_addr" '
'"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
access_log /dev/stdout main;
# managesieve
server {
listen 14190;
resolver {{ RESOLVER }} valid=30s;
proxy_connect_timeout 1s;
proxy_timeout 1m;
proxy_protocol on;
proxy_pass {{ IMAP_ADDRESS }}:4190;
}
}
mail {
server_name {{ HOSTNAMES.split(",")[0] }};
auth_http http://127.0.0.1:8000/auth/email;

View File

@ -55,3 +55,7 @@ conf.jinja("/conf/proxy.conf", args, "/etc/nginx/proxy.conf")
conf.jinja("/conf/nginx.conf", args, "/etc/nginx/nginx.conf")
if os.path.exists("/var/run/nginx.pid"):
os.system("nginx -s reload")
conf.jinja("/login.lua", args, "/etc/dovecot/login.lua")
conf.jinja("/proxy.conf", args, "/etc/dovecot/proxy.conf")
if os.path.exists("/run/dovecot/master.pid"):
os.system("doveadm reload")

38
core/nginx/login.lua Normal file
View File

@ -0,0 +1,38 @@
function script_init()
return 0
end
function script_deinit()
end
local http_client = dovecot.http.client {
timeout = 2000;
max_attempts = 3;
}
function auth_passdb_lookup(req)
local auth_request = http_client:request {
url = "http://{{ ADMIN_ADDRESS }}/internal/auth/email";
}
auth_request:add_header('Auth-Port', req.local_port)
auth_request:add_header('Auth-User', req.user)
auth_request:add_header('Auth-Pass', req.password)
auth_request:add_header('Auth-Protocol', 'sieve')
auth_request:add_header('Client-IP', req.remote_ip)
auth_request:add_header('Auth-SSL', req.secured)
auth_request:add_header('Auth-Method', req.mechanism)
local auth_response = auth_request:submit()
local resp_status = auth_response:status()
if resp_status == 200
then
if auth_response:header('Auth-Status') == 'OK'
then
return dovecot.auth.PASSDB_RESULT_OK, "proxy=y host={{ IMAP_ADDRESS }} nopassword=Y"
else
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, ""
end
else
return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, ""
end
end

62
core/nginx/proxy.conf Normal file
View File

@ -0,0 +1,62 @@
###############
# General
###############
log_path = /dev/stderr
protocols = sieve
postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }}
hostname = {{ HOSTNAMES.split(",")[0] }}
submission_host = {{ FRONT_ADDRESS }}
#instance_name = managesieveproxy
#base_dir = /run/dovecot2
default_internal_user = dovecot
default_login_user = mail
default_internal_group = dovecot
haproxy_trusted_networks = {% if REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %}{{ from_ip }} {% endfor %}{% endif %}
###############
# Authentication
###############
auth_username_chars =
auth_mechanisms = plain login
{% if TLS %}
ssl = required
ssl_cert = <{{ TLS[0] }}
ssl_key = <{{ TLS[1] }}
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
ssl_alt_cert = <{{ TLS[2] }}
ssl_alt_key = <{{ TLS[3] }}
{% endif %}
# intermediate configuration
ssl_min_protocol = TLSv1.2
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl_prefer_server_ciphers = no
ssl_dh = </conf/dhparam.pem
{% else %}
disable_plaintext_auth = no
protocol sieve {
ssl = no
}
{% endif %}
passdb {
driver = lua
args = file=/etc/dovecot/login.lua blocking=yes
}
service auth-worker {
user = $default_internal_user
group = $default_internal_group
}
service managesieve-login {
executable = managesieve-login
inet_listener sieve {
port = 4190
{% if PROXY_PROTOCOL in ['all', 'mail'] %}
haproxy = yes
{% endif %}
}
}

View File

@ -13,4 +13,5 @@ elif os.environ["TLS_FLAVOR"] in [ "mail", "cert" ]:
subprocess.Popen(["/certwatcher.py"])
subprocess.call(["/config.py"])
os.system("dovecot -c /etc/dovecot/proxy.conf")
os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"])

View File

@ -63,7 +63,7 @@ address for your mail server and that you have a dedicated hostname
with forward and reverse DNS entries for this IP address.
Also, your host must not listen on ports ``25``, ``80``, ``110``, ``143``,
``443``, ``465``, ``587``, ``993`` or ``995`` as these are used by Mailu
``443``, ``465``, ``587``, ``993``, ``995`` nor ``4190`` as these are used by Mailu
services. Therefore, you should disable or uninstall any program that is
listening on these ports (or have them listen on a different port). For
instance, on a default Debian install:

View File

@ -26,7 +26,7 @@ Main features include:
- **Standard email server**, IMAP and IMAP+, SMTP and Submission with autoconfiguration profiles for clients
- **Advanced email features**, aliases, domain aliases, custom routing
- **Web access**, multiple Webmails and administration interface
- **User features**, aliases, auto-reply, auto-forward, fetched accounts
- **User features**, aliases, auto-reply, auto-forward, fetched accounts, managesieve
- **Admin features**, global admins, announcements, per-domain delegation, quotas
- **Security**, enforced TLS, DANE, MTA-STS, Letsencrypt!, outgoing DKIM, anti-virus scanner, `Snuffleupagus <https://github.com/jvoisin/snuffleupagus/>`_, block malicious attachments
- **Antispam**, auto-learn, greylisting, DMARC and SPF, anti-spoofing

View File

@ -30,7 +30,7 @@ services:
options:
tag: mailu-front
ports:
{% for port in (80, 443, 25, 465, 587, 110, 995, 143, 993) %}
{% for port in (80, 443, 25, 465, 587, 110, 995, 143, 993, 4190) %}
{% if bind4 %}
- "{{ bind4 }}:{{ port }}:{{ port }}"
{% endif %}

View File

@ -0,0 +1 @@
Add support for managesieve

View File

@ -24,7 +24,14 @@ $config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
// Sieve script management
$config['managesieve_host'] = '{{ FRONT_ADDRESS or "front" }}:14190';
$config['managesieve_host'] = 'tls://{{ FRONT_ADDRESS or "front" }}:4190';
$config['managesieve_conn_options'] = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
),
);
$config['managesieve_mbox_encoding'] = 'UTF8';
// roundcube customization

View File

@ -33,13 +33,13 @@
},
"Sieve": {
"host": "{{ FRONT_ADDRESS }}",
"port": 14190,
"secure": 0,
"port": 4190,
"type": 2,
"shortLogin": false,
"ssl": {
"verify_peer": false,
"verify_peer_name": false,
"allow_self_signed": false,
"allow_self_signed": true,
"SNI_enabled": true,
"disable_compression": true,
"security_level": 1