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/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..bf116f4b --- /dev/null +++ b/core/admin/mailu/internal/views/autoconfig.py @@ -0,0 +1,183 @@ +from mailu.internal import internal + +from flask import current_app as app +import flask +import xmltodict + +@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) + +@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=['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'] + 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 + + + SMTP + {hostname} + 465 + {email} + on + off + on + + + + ''' + return flask.Response(xml, mimetype='text/xml', status=200) + except: + return flask.abort(400) + +@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/admin/mailu/models.py b/core/admin/mailu/models.py index 48b47ea5..436877f7 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -255,20 +255,23 @@ class Domain(Base): """ return list of auto configuration records (RFC6186) """ hostname = app.config['HOSTNAME'] protocols = [ - ('submission', 587), - ('imap', 143), - ('pop3', 110), + ('imap', 143, 20), + ('pop3', 110, 20), + ('submission', 587, 20), ] if app.config['TLS_FLAVOR'] != 'notls': protocols.extend([ - ('imaps', 993), - ('pop3s', 995), + ('autodiscover', 443, 10), + ('submissions', 465, 10), + ('imaps', 993, 10), + ('pop3s', 995, 10), ]) - return list([ - f'_{proto}._tcp.{self.name}. 600 IN SRV 1 1 {port} {hostname}.' - for proto, port + + 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}.'] @cached_property def dns_tlsa(self): diff --git a/core/admin/mailu/ui/templates/client.html b/core/admin/mailu/ui/templates/client.html index cc4517ad..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) %} @@ -17,7 +18,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 +43,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 %} 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 }}
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
diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf
index 11b7468c..4e6919f8 100644
--- a/core/nginx/conf/nginx.conf
+++ b/core/nginx/conf/nginx.conf
@@ -120,6 +120,30 @@ 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 ^ /internal/autoconfig/mozilla break;
+        include /etc/nginx/proxy.conf;
+        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;
+        proxy_pass http://$admin;
+      }
+      # apple mobileconfig
+      location ~ ^/(apple\.)?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/ {
           proxy_pass http://127.0.0.1:8008;
diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py
index 48316c1c..9c998fe2 100755
--- a/core/nginx/letsencrypt.py
+++ b/core/nginx/letsencrypt.py
@@ -4,10 +4,12 @@ import os
 import time
 import subprocess
 
+hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))
+
 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 +22,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",
diff --git a/docs/faq.rst b/docs/faq.rst
index 4757e6a1..264fbcb8 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
 ----------------
 
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
diff --git a/docs/reverse.rst b/docs/reverse.rst
index 91c6f67e..0f516d26 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) {
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr
       proxy_pass https://localhost:8443;     
diff --git a/towncrier/newsfragments/224.feature b/towncrier/newsfragments/224.feature
new file mode 100644
index 00000000..4200a5da
--- /dev/null
+++ b/towncrier/newsfragments/224.feature
@@ -0,0 +1 @@
+Provide auto-configuration files (autodiscover, autoconfig & mobileconfig); Please update your DNS records