From 523cee1680ab5ef8f2a02e239a5e4f47a2c5b719 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 09:28:10 +0100 Subject: [PATCH 01/29] Autoconfig mozilla-style --- core/admin/mailu/internal/views/__init__.py | 2 +- core/admin/mailu/internal/views/autoconfig.py | 42 +++++++++++++++ core/nginx/conf/nginx.conf | 7 +++ docs/faq.rst | 52 ------------------- 4 files changed, 50 insertions(+), 53 deletions(-) create mode 100644 core/admin/mailu/internal/views/autoconfig.py diff --git a/core/admin/mailu/internal/views/__init__.py b/core/admin/mailu/internal/views/__init__.py index 762b2a38..5ebd6ff8 100644 --- a/core/admin/mailu/internal/views/__init__.py +++ b/core/admin/mailu/internal/views/__init__.py @@ -1,3 +1,3 @@ __all__ = [ - 'auth', 'postfix', 'dovecot', 'fetch', 'rspamd' + 'auth', 'autoconfig', 'postfix', 'dovecot', 'fetch', 'rspamd' ] diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py new file mode 100644 index 00000000..3538e756 --- /dev/null +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -0,0 +1,42 @@ +from mailu.internal import internal + +from flask import current_app as app +import flask + +@internal.route("/autoconfig/mozilla") +def autoconfig_mozilla(): + # https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat + hostname = app.config['HOSTNAME'] + xml = f''' + + +%EMAILDOMAIN% + +Email +Email + + +{hostname} +993 +SSL +%EMAILADDRESS% +password-cleartext + + + +{hostname} +465 +SSL +%EMAILADDRESS% +password-cleartext +true +true + + + +Configure your email client + + +\r\n +''' + return flask.Response(xml, mimetype='text/xml', status=200) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index b9bb20b7..6d416c16 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -120,6 +120,13 @@ http { add_header X-XSS-Protection '1; mode=block'; add_header Referrer-Policy 'same-origin'; + # mozilla autoconfiguration + location ^~ /.well-known/autoconfig/mail/config-v1.1.xml { + rewrite /.well-known/autoconfig/mail/config-v1.1.xml /internal/autoconfig/mozilla break; + include /etc/nginx/proxy.conf; + proxy_pass http://$admin; + } + {% if TLS_FLAVOR == 'mail-letsencrypt' %} location ^~ /.well-known/acme-challenge/ { proxy_pass http://127.0.0.1:8008; diff --git a/docs/faq.rst b/docs/faq.rst index fe105403..4221de88 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -396,58 +396,6 @@ Mailu can serve an `MTA-STS policy`_; To configure it you will need to: .. _`1798`: https://github.com/Mailu/Mailu/issues/1798 .. _`MTA-STS policy`: https://datatracker.ietf.org/doc/html/rfc8461 -How do I setup client autoconfiguration? -```````````````````````````````````````` - -Mailu can serve an `XML file for autoconfiguration`_; To configure it you will need to: - -1. add ``autoconfig.example.com`` to the ``HOSTNAMES`` configuration variable (and ensure that a valid SSL certificate is available for it; this may mean restarting your smtp container) - -2. configure an override with the policy itself; for example, your ``overrides/nginx/autoconfiguration.conf`` could read: - -.. code-block:: bash - - location ^~ /mail/config-v1.1.xml { - return 200 " - - - %EMAILDOMAIN% - - Email - Email - - - mailu.example.com - 993 - SSL - %EMAILADDRESS% - password-cleartext - - - - mailu.example.com - 465 - SSL - %EMAILADDRESS% - password-cleartext - true - true - - - - Configure your email client - - - \r\n"; - } - -3. setup the appropriate DNS/CNAME record (``autoconfig.example.com`` -> ``mailu.example.com``). - -*issue reference:* `224`_. - -.. _`224`: https://github.com/Mailu/Mailu/issues/224 -.. _`XML file for autoconfiguration`: https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat - Technical issues ---------------- From ccd2cad4f1b2dc5caf9d679f27975e885ea65f4e Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 10:00:51 +0100 Subject: [PATCH 02/29] Autodiscovery microsoft style --- core/admin/mailu/internal/views/autoconfig.py | 36 +++++++++++++++++-- core/nginx/conf/nginx.conf | 10 ++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 3538e756..b93794b3 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -37,6 +37,38 @@ def autoconfig_mozilla(): Configure your email client -\r\n -''' +\r\n''' + return flask.Response(xml, mimetype='text/xml', status=200) + +@internal.route("/autoconfig/microsoft") +def autoconfig_microsoft(): + # https://docs.microsoft.com/en-us/previous-versions/office/office-2010/cc511507(v=office.14)?redirectedfrom=MSDN#Anchor_3 + hostname = app.config['HOSTNAME'] + xml = f''' + + + +email +settings + +IMAP +{hostname} +993 +on +off +on +on + + +SMTP +{hostname} +465 +on +off +on +on + + + +\r\n''' return flask.Response(xml, mimetype='text/xml', status=200) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 6d416c16..87775f80 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -121,8 +121,14 @@ http { add_header Referrer-Policy 'same-origin'; # mozilla autoconfiguration - location ^~ /.well-known/autoconfig/mail/config-v1.1.xml { - rewrite /.well-known/autoconfig/mail/config-v1.1.xml /internal/autoconfig/mozilla break; + location ~ ^/(\.well\-known/autoconfig/)?mail/config\-v1\.1\.xml { + rewrite ^ /internal/autoconfig/mozilla break; + include /etc/nginx/proxy.conf; + proxy_pass http://$admin; + } + # microsoft autoconfiguration + location ~* ^/Autodiscover/Autodiscover.xml { + rewrite ^ /internal/autoconfig/microsoft break; include /etc/nginx/proxy.conf; proxy_pass http://$admin; } From cdc92aa65be068c1e4b79d6d238698ba3887bfb4 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 10:29:11 +0100 Subject: [PATCH 03/29] Mobileconfig apple style --- core/admin/mailu/internal/views/autoconfig.py | 89 +++++++++++++++++++ core/nginx/conf/nginx.conf | 6 ++ 2 files changed, 95 insertions(+) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index b93794b3..7c549900 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -72,3 +72,92 @@ def autoconfig_microsoft(): \r\n''' return flask.Response(xml, mimetype='text/xml', status=200) + +@internal.route("/autoconfig/apple") +def autoconfig_apple(): + # https://developer.apple.com/business/documentation/Configuration-Profile-Reference.pdf + hostname = app.config['HOSTNAME'] + sitename = app.config['SITENAME'] + xml = f''' + + + +PayloadContent + + +EmailAccountDescription +{sitename} +EmailAccountName +{hostname} +EmailAccountType +EmailTypeIMAP +EmailAddress + +IncomingMailServerAuthentication +EmailAuthPassword +IncomingMailServerHostName +{hostname} +IncomingMailServerPortNumber +993 +IncomingMailServerUseSSL + +IncomingMailServerUsername + +IncomingPassword + +OutgoingMailServerAuthentication +EmailAuthPassword +OutgoingMailServerHostName +{hostname} +OutgoingMailServerPortNumber +465 +OutgoingMailServerUseSSL + +OutgoingMailServerUsername + +OutgoingPasswordSameAsIncomingPassword + +PayloadDescription +{sitename} +PayloadDisplayName +{hostname} +PayloadIdentifier +{hostname}.email +PayloadOrganization + +PayloadType +com.apple.mail.managed +PayloadUUID +72e152e2-d285-4588-9741-25bdd50c4d11 +PayloadVersion +1 +PreventAppSheet + +PreventMove + +SMIMEEnabled + +disableMailRecentsSyncing + + + +PayloadDescription +{hostname} - E-Mail Account Configuration +PayloadDisplayName +E-Mail Account {hostname} +PayloadIdentifier +E-Mail Account {hostname} +PayloadOrganization +{hostname} +PayloadRemovalDisallowed + +PayloadType +Configuration +PayloadUUID +56db43a5-d29e-4609-a908-dce94d0be48e +PayloadVersion +1 + +\r\n''' + return flask.Response(xml, mimetype='text/xml', status=200) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 87775f80..4edb5dcd 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -132,6 +132,12 @@ http { include /etc/nginx/proxy.conf; proxy_pass http://$admin; } + # apple mobileconfig + location ^~ /mobileconfig { + rewrite ^ /internal/autoconfig/apple break; + include /etc/nginx/proxy.conf; + proxy_pass http://$admin; + } {% if TLS_FLAVOR == 'mail-letsencrypt' %} location ^~ /.well-known/acme-challenge/ { From d7a609edc067748eab211da8208aee21a4dc2f7b Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 10:35:46 +0100 Subject: [PATCH 04/29] towncrier --- towncrier/newsfragments/224.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 towncrier/newsfragments/224.feature diff --git a/towncrier/newsfragments/224.feature b/towncrier/newsfragments/224.feature new file mode 100644 index 00000000..c086c286 --- /dev/null +++ b/towncrier/newsfragments/224.feature @@ -0,0 +1 @@ +Provide auto-configuration files (autodiscover, autoconfig & mobileconfig) From c817eaf60891452e548128713d9430796bef16b5 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 11:40:35 +0100 Subject: [PATCH 05/29] Add the SRV record for autodiscover --- core/admin/mailu/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 48b47ea5..88223cf7 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -258,6 +258,7 @@ class Domain(Base): ('submission', 587), ('imap', 143), ('pop3', 110), + ('autodiscover', 443), ] if app.config['TLS_FLAVOR'] != 'notls': protocols.extend([ From 2b62a6327ab8300c3e271c4c5418164909b3b4a1 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 11:49:30 +0100 Subject: [PATCH 06/29] Do explicit TLS where possible --- core/admin/mailu/models.py | 17 ++++++++++++++--- .../mailu/ui/templates/domain/details.html | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 88223cf7..dad6986c 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -255,16 +255,27 @@ class Domain(Base): """ return list of auto configuration records (RFC6186) """ hostname = app.config['HOSTNAME'] protocols = [ - ('submission', 587), - ('imap', 143), - ('pop3', 110), ('autodiscover', 443), ] if app.config['TLS_FLAVOR'] != 'notls': protocols.extend([ + ('submission', 0), + ('submissions', 465), + ('imap', 0), + ('pop3', 0), ('imaps', 993), ('pop3s', 995), ]) + else: + protocols.extend([ + ('submission', 587), + ('submissions', 0), + ('imap', 143), + ('pop3', 110), + ('imaps', 0), + ('pop3s', 0), + ]) + return list([ f'_{proto}._tcp.{self.name}. 600 IN SRV 1 1 {port} {hostname}.' for proto, port diff --git a/core/admin/mailu/ui/templates/domain/details.html b/core/admin/mailu/ui/templates/domain/details.html index a30b9357..28f3f570 100644 --- a/core/admin/mailu/ui/templates/domain/details.html +++ b/core/admin/mailu/ui/templates/domain/details.html @@ -60,7 +60,7 @@ {%- endif %} - {% trans %}DNS client auto-configuration (RFC6186) entries{% endtrans %} + {% trans %}DNS client auto-configuration entries{% endtrans %} {{ macros.clip("dns_autoconfig") }}
 {%- for line in domain.dns_autoconfig %}
 {{ line }}

From a3f9e2beee21d1b1145efba3042da9b542fb3c4a Mon Sep 17 00:00:00 2001
From: Florent Daigniere 
Date: Thu, 10 Mar 2022 11:53:32 +0100
Subject: [PATCH 07/29] Use priorities instead

---
 core/admin/mailu/models.py | 27 +++++++++------------------
 1 file changed, 9 insertions(+), 18 deletions(-)

diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py
index dad6986c..fd182570 100644
--- a/core/admin/mailu/models.py
+++ b/core/admin/mailu/models.py
@@ -255,30 +255,21 @@ class Domain(Base):
         """ return list of auto configuration records (RFC6186) """
         hostname = app.config['HOSTNAME']
         protocols = [
-            ('autodiscover', 443),
+            ('imap', 143, 20),
+            ('pop3', 110, 20),
+            ('submission', 587, 20),
         ]
         if app.config['TLS_FLAVOR'] != 'notls':
             protocols.extend([
-                ('submission', 0),
-                ('submissions', 465),
-                ('imap', 0),
-                ('pop3', 0),
-                ('imaps', 993),
-                ('pop3s', 995),
-            ])
-        else:
-            protocols.extend([
-                ('submission', 587),
-                ('submissions', 0),
-                ('imap', 143),
-                ('pop3', 110),
-                ('imaps', 0),
-                ('pop3s', 0),
+                ('autodiscover', 443, 10),
+                ('submissions', 465, 20, 10),
+                ('imaps', 993, 10),
+                ('pop3s', 995, 10),
             ])
 
         return list([
-            f'_{proto}._tcp.{self.name}. 600 IN SRV 1 1 {port} {hostname}.'
-            for proto, port
+            f'_{proto}._tcp.{self.name}. 600 IN SRV {prio} 1 {port} {hostname}.'
+            for proto, port, prio
             in protocols
         ])
 

From 81b592f3cb7474df62b2c9273c141959bd9fe6e6 Mon Sep 17 00:00:00 2001
From: Florent Daigniere 
Date: Thu, 10 Mar 2022 12:31:01 +0100
Subject: [PATCH 08/29] try to get LE certs for the new names

---
 core/admin/mailu/models.py |  2 +-
 core/nginx/letsencrypt.py  | 10 ++++++++--
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py
index fd182570..cd5db559 100644
--- a/core/admin/mailu/models.py
+++ b/core/admin/mailu/models.py
@@ -271,7 +271,7 @@ class Domain(Base):
             f'_{proto}._tcp.{self.name}. 600 IN SRV {prio} 1 {port} {hostname}.'
             for proto, port, prio
             in protocols
-        ])
+        ])+[f'autoconfig.{self.name}. 600 IN CNAME {hostname}.']
 
     @cached_property
     def dns_tlsa(self):
diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py
index 48316c1c..b87b2fea 100755
--- a/core/nginx/letsencrypt.py
+++ b/core/nginx/letsencrypt.py
@@ -4,10 +4,16 @@ import os
 import time
 import subprocess
 
+hostnames = list(set(os.environ['HOSTNAMES'].split(',')))
+for hostname in hostnames:
+    if not hostname.startswith('autoconfig.'):
+        hostnames.append(f'autoconfig.{hostname}')
+hostnames = ','.join(set(hostnames))
+
 command = [
     "certbot",
     "-n", "--agree-tos", # non-interactive
-    "-d", os.environ["HOSTNAMES"],
+    "-d", hostnames, "--expand", "--allow-subset-of-names",
     "-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
     "certonly", "--standalone",
     "--cert-name", "mailu",
@@ -20,7 +26,7 @@ command = [
 command2 = [
     "certbot",
     "-n", "--agree-tos", # non-interactive
-    "-d", os.environ["HOSTNAMES"],
+    "-d", hostnames, "--expand", "--allow-subset-of-names",
     "-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
     "certonly", "--standalone",
     "--cert-name", "mailu-ecdsa",

From 7970dd7145ae299dfd93b36b5d9448820187ebbc Mon Sep 17 00:00:00 2001
From: Florent Daigniere 
Date: Thu, 10 Mar 2022 12:34:44 +0100
Subject: [PATCH 09/29] Ask users to check their DNS records

---
 towncrier/newsfragments/224.feature | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/towncrier/newsfragments/224.feature b/towncrier/newsfragments/224.feature
index c086c286..4200a5da 100644
--- a/towncrier/newsfragments/224.feature
+++ b/towncrier/newsfragments/224.feature
@@ -1 +1 @@
-Provide auto-configuration files (autodiscover, autoconfig & mobileconfig)
+Provide auto-configuration files (autodiscover, autoconfig & mobileconfig); Please update your DNS records

From 3a56525e212d0305070186f39fe6b24710526ee9 Mon Sep 17 00:00:00 2001
From: Florent Daigniere 
Date: Thu, 10 Mar 2022 13:36:08 +0100
Subject: [PATCH 10/29] As discussed on #mailu-dev

Don't attempt to guess what the user wants
---
 core/nginx/letsencrypt.py | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py
index b87b2fea..9f445470 100755
--- a/core/nginx/letsencrypt.py
+++ b/core/nginx/letsencrypt.py
@@ -4,11 +4,7 @@ import os
 import time
 import subprocess
 
-hostnames = list(set(os.environ['HOSTNAMES'].split(',')))
-for hostname in hostnames:
-    if not hostname.startswith('autoconfig.'):
-        hostnames.append(f'autoconfig.{hostname}')
-hostnames = ','.join(set(hostnames))
+hostnames = ','.join(set(os.environ['HOSTNAMES'].split(',')))
 
 command = [
     "certbot",

From 0bccb5045c1f2ccd2f8ac13fc7e03faa43049912 Mon Sep 17 00:00:00 2001
From: Florent Daigniere 
Date: Thu, 10 Mar 2022 14:44:55 +0100
Subject: [PATCH 11/29] STARTTLS is a bad idea

---
 core/admin/mailu/ui/templates/client.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/admin/mailu/ui/templates/client.html b/core/admin/mailu/ui/templates/client.html
index cc4517ad..e5d4eab8 100644
--- a/core/admin/mailu/ui/templates/client.html
+++ b/core/admin/mailu/ui/templates/client.html
@@ -17,7 +17,7 @@
     
     
       {% trans %}TCP port{% endtrans %}
-      {{ "143" if config["TLS_FLAVOR"] == "notls" else "993 (TLS) or 143 (STARTTLS)" }}
+      {{ "143" if config["TLS_FLAVOR"] == "notls" else "993 (TLS)" }}
     
     
       {% trans %}Server name{% endtrans %}
@@ -42,7 +42,7 @@
     
     
       {% trans %}TCP port{% endtrans %}
-      {{ "25" if config["TLS_FLAVOR"] == "notls" else "465 (TLS) or 587 (STARTTLS)" }}
+      {{ "25" if config["TLS_FLAVOR"] == "notls" else "465 (TLS)" }}
     
     
       {% trans %}Server name{% endtrans %}

From 6fc1273b58c14fac5fbd9ee7a5d84b1bce54dc05 Mon Sep 17 00:00:00 2001
From: Florent Daigniere 
Date: Thu, 10 Mar 2022 14:51:14 +0100
Subject: [PATCH 12/29] Add a link to autoconfigure apple devices

---
 core/admin/mailu/ui/templates/client.html | 1 +
 core/nginx/conf/nginx.conf                | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/admin/mailu/ui/templates/client.html b/core/admin/mailu/ui/templates/client.html
index e5d4eab8..bf0ba64e 100644
--- a/core/admin/mailu/ui/templates/client.html
+++ b/core/admin/mailu/ui/templates/client.html
@@ -9,6 +9,7 @@
 {%- endblock %}
 
 {%- block content %}
+
If you use an Apple device, click here to autoconfigure it.
{%- call macros.table(title=_("Incoming mail"), datatable=False) %} diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 4edb5dcd..3e0ae94c 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -133,7 +133,7 @@ http { proxy_pass http://$admin; } # apple mobileconfig - location ^~ /mobileconfig { + location ~ ^/(apple\.)?mobileconfig { rewrite ^ /internal/autoconfig/apple break; include /etc/nginx/proxy.conf; proxy_pass http://$admin; From d677c465a778bdafa6fe729a8ef755bff8262900 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 10 Mar 2022 17:32:17 +0100 Subject: [PATCH 13/29] Handle spaces too --- core/nginx/letsencrypt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py index 9f445470..23397d2a 100755 --- a/core/nginx/letsencrypt.py +++ b/core/nginx/letsencrypt.py @@ -4,7 +4,7 @@ import os import time import subprocess -hostnames = ','.join(set(os.environ['HOSTNAMES'].split(','))) +hostnames = ','.join(set([host.strip() for host in os.environ['HOSTNAMES'].split(',')])) command = [ "certbot", From 71897f4ff0c70d4dcc167decdba8b0a7da36b258 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 14 Mar 2022 09:14:32 +0100 Subject: [PATCH 14/29] Doh --- core/admin/mailu/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index cd5db559..c939ef68 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -262,7 +262,7 @@ class Domain(Base): if app.config['TLS_FLAVOR'] != 'notls': protocols.extend([ ('autodiscover', 443, 10), - ('submissions', 465, 20, 10), + ('submissions', 465, 10), ('imaps', 993, 10), ('pop3s', 995, 10), ]) From 57fbfc68e6086928ad4380bb7dfdfb88edbf6aad Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 14 Mar 2022 09:25:13 +0100 Subject: [PATCH 15/29] Update the reverse proxy doc --- docs/reverse.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reverse.rst b/docs/reverse.rst index 8d0dcc8e..7d994980 100644 --- a/docs/reverse.rst +++ b/docs/reverse.rst @@ -59,14 +59,14 @@ Then on your own frontend, point to these local ports. In practice, you only nee REAL_IP_FROM=x.x.x.x,y.y.y.y.y #x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu. -Because the admin interface is served as ``/admin``, the Webmail as ``/webmail``, the single sign on page as ``/sso``, webdav as ``/webdav`` and the static files endpoint as ``/static``, you may also want to use a single virtual host and serve other applications (still Nginx): +Because the admin interface is served as ``/admin``, the Webmail as ``/webmail``, the single sign on page as ``/sso``, webdav as ``/webdav``, the client-autoconfiguration and the static files endpoint as ``/static``, you may also want to use a single virtual host and serve other applications (still Nginx): .. code-block:: nginx server { # [...] here goes your standard configuration - location ~ ^/(admin|sso|static|webdav|webmail) { + location ~ ^/(admin|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover.xml) { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr proxy_pass https://localhost:8443; From 2cfde6eacdda4867ed3cf88c23925d06e4a244c2 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 14 Mar 2022 16:33:17 +0100 Subject: [PATCH 16/29] Case sensitivity --- docs/reverse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reverse.rst b/docs/reverse.rst index 7d994980..80ca1829 100644 --- a/docs/reverse.rst +++ b/docs/reverse.rst @@ -66,7 +66,7 @@ Because the admin interface is served as ``/admin``, the Webmail as ``/webmail`` server { # [...] here goes your standard configuration - location ~ ^/(admin|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover.xml) { + location ~* ^/(admin|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover.xml) { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr proxy_pass https://localhost:8443; From c50750054b1c4a8ae7fae8f59b45951fc230c759 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 14 Mar 2022 16:34:54 +0100 Subject: [PATCH 17/29] Allow POST --- core/admin/mailu/internal/views/autoconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 7c549900..6de9ff9d 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -40,7 +40,7 @@ def autoconfig_mozilla(): \r\n''' return flask.Response(xml, mimetype='text/xml', status=200) -@internal.route("/autoconfig/microsoft") +@internal.route("/autoconfig/microsoft", methods=['GET', 'POST']) def autoconfig_microsoft(): # https://docs.microsoft.com/en-us/previous-versions/office/office-2010/cc511507(v=office.14)?redirectedfrom=MSDN#Anchor_3 hostname = app.config['HOSTNAME'] From 9a2d8d63a363b16fa7fd1cd6b809f1edf2f8dd16 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 15 Mar 2022 08:57:48 +0100 Subject: [PATCH 18/29] Search and replace wasn't a good idea --- core/admin/mailu/internal/views/autoconfig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 6de9ff9d..a9a1925a 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -45,8 +45,8 @@ def autoconfig_microsoft(): # https://docs.microsoft.com/en-us/previous-versions/office/office-2010/cc511507(v=office.14)?redirectedfrom=MSDN#Anchor_3 hostname = app.config['HOSTNAME'] xml = f''' - - + + email settings From 184c9bc56682ccde1cf3bc28f7810acc10f23bdc Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 15 Mar 2022 10:26:29 +0100 Subject: [PATCH 19/29] Add json redirect --- core/admin/mailu/internal/views/autoconfig.py | 10 ++++++++++ core/nginx/conf/nginx.conf | 5 +++++ docs/reverse.rst | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index a9a1925a..a4816f22 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -40,6 +40,16 @@ def autoconfig_mozilla(): \r\n''' return flask.Response(xml, mimetype='text/xml', status=200) +@internal.route("/autoconfig/microsoft.json") +def autoconfig_microsoft_json(): + proto = flask.request.args.get('Protocol', 'Autodiscoverv1') + if proto == 'Autodiscoverv1': + hostname = app.config['HOSTNAME'] + json = f'{"Protocol":"Autodiscoverv1","Url":"https://{hostname}/autodiscover/autodiscover.xml"}' + return flask.Response(json, mimetype='application/json', status=200) + else + return flask.abort(404) + @internal.route("/autoconfig/microsoft", methods=['GET', 'POST']) def autoconfig_microsoft(): # https://docs.microsoft.com/en-us/previous-versions/office/office-2010/cc511507(v=office.14)?redirectedfrom=MSDN#Anchor_3 diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 3e0ae94c..66b1a673 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -127,6 +127,11 @@ http { proxy_pass http://$admin; } # microsoft autoconfiguration + location ~* ^/Autodiscover/Autodiscover.json { + rewrite ^ /internal/autoconfig/microsoft.json break; + include /etc/nginx/proxy.conf; + proxy_pass http://$admin; + } location ~* ^/Autodiscover/Autodiscover.xml { rewrite ^ /internal/autoconfig/microsoft break; include /etc/nginx/proxy.conf; diff --git a/docs/reverse.rst b/docs/reverse.rst index 80ca1829..f9b73e8f 100644 --- a/docs/reverse.rst +++ b/docs/reverse.rst @@ -66,7 +66,7 @@ Because the admin interface is served as ``/admin``, the Webmail as ``/webmail`` server { # [...] here goes your standard configuration - location ~* ^/(admin|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover.xml) { + location ~* ^/(admin|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover) { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr proxy_pass https://localhost:8443; From 373e6d2161ec5ca8c565e0ab8bdfed21053444d2 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 15 Mar 2022 12:41:31 +0100 Subject: [PATCH 20/29] doh --- core/admin/mailu/internal/views/autoconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index a4816f22..9158f185 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -47,7 +47,7 @@ def autoconfig_microsoft_json(): hostname = app.config['HOSTNAME'] json = f'{"Protocol":"Autodiscoverv1","Url":"https://{hostname}/autodiscover/autodiscover.xml"}' return flask.Response(json, mimetype='application/json', status=200) - else + else: return flask.abort(404) @internal.route("/autoconfig/microsoft", methods=['GET', 'POST']) From c6c444cfa71ef0b5e934664b35639a0b14be4051 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 16 Mar 2022 11:02:39 +0100 Subject: [PATCH 21/29] simplify --- core/admin/mailu/internal/views/autoconfig.py | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 9158f185..85d3dbb0 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -56,31 +56,29 @@ def autoconfig_microsoft(): hostname = app.config['HOSTNAME'] xml = f''' - - -email -settings - -IMAP -{hostname} -993 -on -off -on -on - - -SMTP -{hostname} -465 -on -off -on -on - - - -\r\n''' + + + email + settings + + IMAP + {hostname} + 993 + on + off + TLS + + + SMTP + {hostname} + 465 + on + off + TLS + + + +''' return flask.Response(xml, mimetype='text/xml', status=200) @internal.route("/autoconfig/apple") From 14931c4acddb2448a9ed9b051d4a9ed71b6752f3 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 16 Mar 2022 14:14:03 +0100 Subject: [PATCH 22/29] doh --- core/admin/mailu/internal/views/autoconfig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 85d3dbb0..d68d07bc 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -45,8 +45,8 @@ def autoconfig_microsoft_json(): proto = flask.request.args.get('Protocol', 'Autodiscoverv1') if proto == 'Autodiscoverv1': hostname = app.config['HOSTNAME'] - json = f'{"Protocol":"Autodiscoverv1","Url":"https://{hostname}/autodiscover/autodiscover.xml"}' - return flask.Response(json, mimetype='application/json', status=200) + json = f'"Protocol":"Autodiscoverv1","Url":"https://{hostname}/autodiscover/autodiscover.xml"' + return flask.Response('{'+json+'}', mimetype='application/json', status=200) else: return flask.abort(404) From 3e6f3a95a4c3367b70d0ee08f02e1903c9667bd7 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 16 Mar 2022 14:43:14 +0100 Subject: [PATCH 23/29] Reflect the data from the POST --- core/admin/mailu/internal/views/autoconfig.py | 65 +++++++++++-------- core/admin/requirements-prod.txt | 1 + core/admin/requirements.txt | 1 + 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index d68d07bc..07797281 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -2,6 +2,7 @@ from mailu.internal import internal from flask import current_app as app import flask +import xmltodict @internal.route("/autoconfig/mozilla") def autoconfig_mozilla(): @@ -50,36 +51,48 @@ def autoconfig_microsoft_json(): else: return flask.abort(404) -@internal.route("/autoconfig/microsoft", methods=['GET', 'POST']) +@internal.route("/autoconfig/microsoft", methods=['POST']) def autoconfig_microsoft(): # https://docs.microsoft.com/en-us/previous-versions/office/office-2010/cc511507(v=office.14)?redirectedfrom=MSDN#Anchor_3 hostname = app.config['HOSTNAME'] - xml = f''' - - - - email - settings - - IMAP - {hostname} - 993 - on - off - TLS - - - SMTP - {hostname} - 465 - on - off - TLS + try: + xmlRequest = (flask.request.data).decode("utf-8") + xml = xmltodict.parse(xmlRequest[xmlRequest.find('<'):xmlRequest.rfind('>')+1]) + schema = xml['Autodiscover']['Request']['AcceptableResponseSchema'] + if schema != 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a': + return flask.abort(404) + email = xml['Autodiscover']['Request']['EMailAddress'] + xml = f''' + + + + email + settings + + IMAP + {hostname} + 993 + {email} + on + off + on - - -''' - return flask.Response(xml, mimetype='text/xml', status=200) + + SMTP + {hostname} + 465 + {email} + on + off + on + + + + ''' + return flask.Response(xml, mimetype='text/xml', status=200) + except: + pass + return flask.abort(400) @internal.route("/autoconfig/apple") def autoconfig_apple(): diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index d6c7aca3..f14e484c 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -73,3 +73,4 @@ webencodings==0.5.1 Werkzeug==2.0.2 WTForms==2.3.3 WTForms-Components==0.10.5 +xmltodict==0.12.0 diff --git a/core/admin/requirements.txt b/core/admin/requirements.txt index 65130a8c..61fbd92c 100644 --- a/core/admin/requirements.txt +++ b/core/admin/requirements.txt @@ -25,3 +25,4 @@ srslib marshmallow flask-marshmallow marshmallow-sqlalchemy +xmltodict From 9bc963f19bda5f3078cfba94db6c568a6f72fbca Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 16 Mar 2022 14:58:43 +0100 Subject: [PATCH 24/29] don't think the escaping is required but it was there --- core/admin/mailu/internal/views/autoconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 07797281..830d2e0b 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -64,7 +64,7 @@ def autoconfig_microsoft(): email = xml['Autodiscover']['Request']['EMailAddress'] xml = f''' - + email settings From 2d1c5f7910dd6c4257445b80623858a90fb4bcdc Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 16 Mar 2022 17:05:23 +0100 Subject: [PATCH 25/29] document --- README.md | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c19ad78..0d8402f5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Features Main features include: -- **Standard email server**, IMAP and IMAP+, SMTP and Submission +- **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 diff --git a/docs/index.rst b/docs/index.rst index 238cea5d..6a3c42b7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,7 +23,7 @@ popular groupware. Main features include: -- **Standard email server**, IMAP and IMAP+, SMTP and Submission +- **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 From 6d80eea649416e8f0f184c9a7fce245a54ee595d Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 17 Mar 2022 11:35:31 +0100 Subject: [PATCH 26/29] ghostwheel42's suggestion --- core/nginx/letsencrypt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py index 23397d2a..9c998fe2 100755 --- a/core/nginx/letsencrypt.py +++ b/core/nginx/letsencrypt.py @@ -4,7 +4,7 @@ import os import time import subprocess -hostnames = ','.join(set([host.strip() for host in os.environ['HOSTNAMES'].split(',')])) +hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(','))) command = [ "certbot", From 3aa735cc2dcae1d4a03ee5f3770492c7cfa0711e Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 17 Mar 2022 11:37:01 +0100 Subject: [PATCH 27/29] ghostwheel42's suggestion --- core/admin/mailu/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index c939ef68..436877f7 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -267,11 +267,11 @@ class Domain(Base): ('pop3s', 995, 10), ]) - return list([ + return [ f'_{proto}._tcp.{self.name}. 600 IN SRV {prio} 1 {port} {hostname}.' for proto, port, prio in protocols - ])+[f'autoconfig.{self.name}. 600 IN CNAME {hostname}.'] + ]+[f'autoconfig.{self.name}. 600 IN CNAME {hostname}.'] @cached_property def dns_tlsa(self): From 83140322e0a98f2a04340c0327ab6168e500caea Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 17 Mar 2022 11:38:22 +0100 Subject: [PATCH 28/29] ghostwheel42's suggestion --- core/admin/mailu/internal/views/autoconfig.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 830d2e0b..65271eeb 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -91,8 +91,7 @@ def autoconfig_microsoft(): ''' return flask.Response(xml, mimetype='text/xml', status=200) except: - pass - return flask.abort(400) + return flask.abort(400) @internal.route("/autoconfig/apple") def autoconfig_apple(): From ce9dc3a335be8b3fb148dcc4210bab69a79a10e1 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 17 Mar 2022 11:39:57 +0100 Subject: [PATCH 29/29] ghostwheel42's suggestion --- core/admin/mailu/internal/views/autoconfig.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/admin/mailu/internal/views/autoconfig.py b/core/admin/mailu/internal/views/autoconfig.py index 65271eeb..bf116f4b 100644 --- a/core/admin/mailu/internal/views/autoconfig.py +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -8,15 +8,15 @@ import xmltodict def autoconfig_mozilla(): # https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat hostname = app.config['HOSTNAME'] - xml = f''' - - + xml = f''' + + %EMAILDOMAIN% Email Email - + {hostname} 993 SSL @@ -24,7 +24,7 @@ def autoconfig_mozilla(): password-cleartext - + {hostname} 465 SSL @@ -34,8 +34,8 @@ def autoconfig_mozilla(): true - -Configure your email client + +Configure your email client \r\n''' @@ -62,9 +62,9 @@ def autoconfig_microsoft(): if schema != 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a': return flask.abort(404) email = xml['Autodiscover']['Request']['EMailAddress'] - xml = f''' - - + xml = f''' + + email settings @@ -98,10 +98,10 @@ def autoconfig_apple(): # https://developer.apple.com/business/documentation/Configuration-Profile-Reference.pdf hostname = app.config['HOSTNAME'] sitename = app.config['SITENAME'] - xml = f''' - - + xml = f''' + + PayloadContent