diff --git a/.mergify.yml b/.mergify.yml index a950b0ca..927bfc3e 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -7,7 +7,6 @@ pull_request_rules: actions: merge: method: merge - strict: true dismiss_reviews: approved: true @@ -20,6 +19,5 @@ pull_request_rules: actions: merge: method: merge - strict: true dismiss_reviews: approved: true diff --git a/CHANGELOG.md b/CHANGELOG.md index f3bcce93..8277743d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ v1.6.0 - unreleased - Enhancement: Move Mailu Docker network to a fixed subnet ([#727](https://github.com/Mailu/Mailu/issues/727)) - Enhancement: Added regex validation for alias username ([#764](https://github.com/Mailu/Mailu/issues/764)) - Enhancement: Update documentation +- Enhancement: Include favicon package ([#801](https://github.com/Mailu/Mailu/issues/801), ([#802](https://github.com/Mailu/Mailu/issues/802)) - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) @@ -107,6 +108,7 @@ v1.6.0 - unreleased - Bug: Hostname resolving in start.py should retry on failure [docker swarm] ([#555](https://github.com/Mailu/Mailu/issues/555)) - Bug: Error when trying to log in with an account without domain ([#585](https://github.com/Mailu/Mailu/issues/585)) - Bug: Fix rainloop permissions ([#637](https://github.com/Mailu/Mailu/issues/637)) +- Bug: Fix broken webmail and logo url in admin ([#792](https://github.com/Mailu/Mailu/issues/792)) v1.5.1 - 2017-11-21 ------------------- diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..856fa5a0 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## What type of PR? + +(Feature, enhancement, bug-fix, documentation) + +## What does this PR do? + +### Related issue(s) +- Mention an issue like: #001 +- Auto close an issue like: closes #001 + +## Prerequistes +Before we can consider review and merge, please make sure the following list is done and checked. +If an entry in not applicable, you can check it or remove it from the list. + +- [ ] In case of feature or enhancement: documentation updated accordingly +- [ ] Unless it's docs or a minor change: place entry in the [changelog](CHANGELOG.md), under the latest un-released version. diff --git a/core/admin/mailu/internal/templates/default.sieve b/core/admin/mailu/internal/templates/default.sieve index d771ee99..5e995611 100644 --- a/core/admin/mailu/internal/templates/default.sieve +++ b/core/admin/mailu/internal/templates/default.sieve @@ -33,5 +33,5 @@ if exists "X-Virus" { } {% if user.reply_active %} -vacation :days 1 :subject "{{ user.reply_subject }}" "{{ user.reply_body }}"; +vacation :days 1 {% if user.displayed_name != "" %}:from "{{ user.displayed_name }} <{{ user.email }}>"{% endif %} :subject "{{ user.reply_subject }}" "{{ user.reply_body }}"; {% endif %} diff --git a/core/admin/mailu/internal/views/dovecot.py b/core/admin/mailu/internal/views/dovecot.py index 463ecc20..f44f59bc 100644 --- a/core/admin/mailu/internal/views/dovecot.py +++ b/core/admin/mailu/internal/views/dovecot.py @@ -6,7 +6,7 @@ import flask import socket import os -@internal.route("/dovecot/passdb/") +@internal.route("/dovecot/passdb/") def dovecot_passdb_dict(user_email): user = models.User.query.get(user_email) or flask.abort(404) allow_nets = [] @@ -20,7 +20,7 @@ def dovecot_passdb_dict(user_email): }) -@internal.route("/dovecot/userdb/") +@internal.route("/dovecot/userdb/") def dovecot_userdb_dict(user_email): user = models.User.query.get(user_email) or flask.abort(404) return flask.jsonify({ @@ -28,7 +28,7 @@ def dovecot_userdb_dict(user_email): }) -@internal.route("/dovecot/quota//", methods=["POST"]) +@internal.route("/dovecot/quota//", methods=["POST"]) def dovecot_quota(ns, user_email): user = models.User.query.get(user_email) or flask.abort(404) if ns == "storage": @@ -37,12 +37,12 @@ def dovecot_quota(ns, user_email): return flask.jsonify(None) -@internal.route("/dovecot/sieve/name/ @@ -28,7 +35,7 @@ class="hold-transition skin-blue sidebar-mini"
{% block navbar %}
-
diff --git a/core/admin/mailu/ui/templates/sidebar.html b/core/admin/mailu/ui/templates/sidebar.html index c3a2ced6..3d945380 100644 --- a/core/admin/mailu/ui/templates/sidebar.html +++ b/core/admin/mailu/ui/templates/sidebar.html @@ -69,7 +69,7 @@
  • {% trans %}Go to{% endtrans %}
  • {% if config["WEBMAIL"] != "none" %}
  • - + {% trans %}Webmail{% endtrans %}
  • diff --git a/core/admin/mailu/ui/templates/user/create.html b/core/admin/mailu/ui/templates/user/create.html index 09e83155..ed7b9884 100644 --- a/core/admin/mailu/ui/templates/user/create.html +++ b/core/admin/mailu/ui/templates/user/create.html @@ -15,6 +15,7 @@ {% call macros.box(_("General")) %} {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} {{ macros.form_fields((form.pw, form.pw2)) }} + {{ macros.form_field(form.displayed_name) }} {{ macros.form_field(form.comment) }} {{ macros.form_field(form.enabled) }} {% endcall %} diff --git a/core/admin/mailu/ui/templates/user/settings.html b/core/admin/mailu/ui/templates/user/settings.html index e14d2d70..b6ade695 100644 --- a/core/admin/mailu/ui/templates/user/settings.html +++ b/core/admin/mailu/ui/templates/user/settings.html @@ -11,6 +11,10 @@ {% block content %}
    {{ form.hidden_tag() }} + + {% call macros.box(title=_("Displayed name")) %} + {{ macros.form_field(form.displayed_name) }} + {% endcall %} {% call macros.box(title=_("Antispam")) %} {{ macros.form_field(form.spam_enabled) }} diff --git a/core/admin/mailu/ui/views/admins.py b/core/admin/mailu/ui/views/admins.py index 7462c545..7daa9d31 100644 --- a/core/admin/mailu/ui/views/admins.py +++ b/core/admin/mailu/ui/views/admins.py @@ -33,7 +33,7 @@ def admin_create(): return flask.render_template('admin/create.html', form=form) -@ui.route('/admin/delete/', methods=['GET', 'POST']) +@ui.route('/admin/delete/', methods=['GET', 'POST']) @access.global_admin @access.confirmation_required("delete admin {admin}") def admin_delete(admin): diff --git a/core/admin/mailu/ui/views/aliases.py b/core/admin/mailu/ui/views/aliases.py index eaab5cad..5e65c86d 100644 --- a/core/admin/mailu/ui/views/aliases.py +++ b/core/admin/mailu/ui/views/aliases.py @@ -36,7 +36,7 @@ def alias_create(domain_name): domain=domain, form=form) -@ui.route('/alias/edit/', methods=['GET', 'POST']) +@ui.route('/alias/edit/', methods=['GET', 'POST']) @access.domain_admin(models.Alias, 'alias') def alias_edit(alias): alias = models.Alias.query.get(alias) or flask.abort(404) @@ -53,7 +53,7 @@ def alias_edit(alias): form=form, alias=alias, domain=alias.domain) -@ui.route('/alias/delete/', methods=['GET', 'POST']) +@ui.route('/alias/delete/', methods=['GET', 'POST']) @access.domain_admin(models.Alias, 'alias') @access.confirmation_required("delete {alias}") def alias_delete(alias): diff --git a/core/admin/mailu/ui/views/fetches.py b/core/admin/mailu/ui/views/fetches.py index c0421146..d9f55404 100644 --- a/core/admin/mailu/ui/views/fetches.py +++ b/core/admin/mailu/ui/views/fetches.py @@ -6,7 +6,7 @@ import flask_login @ui.route('/fetch/list', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/fetch/list/', methods=['GET']) +@ui.route('/fetch/list/', methods=['GET']) @access.owner(models.User, 'user_email') def fetch_list(user_email): user_email = user_email or flask_login.current_user.email @@ -15,7 +15,7 @@ def fetch_list(user_email): @ui.route('/fetch/create', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/fetch/create/', methods=['GET', 'POST']) +@ui.route('/fetch/create/', methods=['GET', 'POST']) @access.owner(models.User, 'user_email') def fetch_create(user_email): user_email = user_email or flask_login.current_user.email diff --git a/core/admin/mailu/ui/views/managers.py b/core/admin/mailu/ui/views/managers.py index 45974f94..746e7367 100644 --- a/core/admin/mailu/ui/views/managers.py +++ b/core/admin/mailu/ui/views/managers.py @@ -38,7 +38,7 @@ def manager_create(domain_name): domain=domain, form=form) -@ui.route('/manager/delete//', methods=['GET', 'POST']) +@ui.route('/manager/delete//', methods=['GET', 'POST']) @access.confirmation_required("remove manager {user_email}") @access.domain_admin(models.Domain, 'domain_name') def manager_delete(domain_name, user_email): diff --git a/core/admin/mailu/ui/views/tokens.py b/core/admin/mailu/ui/views/tokens.py index 3864617b..069587e1 100644 --- a/core/admin/mailu/ui/views/tokens.py +++ b/core/admin/mailu/ui/views/tokens.py @@ -9,7 +9,7 @@ import wtforms_components @ui.route('/token/list', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/token/list/', methods=['GET']) +@ui.route('/token/list/', methods=['GET']) @access.owner(models.User, 'user_email') def token_list(user_email): user_email = user_email or flask_login.current_user.email @@ -18,7 +18,7 @@ def token_list(user_email): @ui.route('/token/create', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/token/create/', methods=['GET', 'POST']) +@ui.route('/token/create/', methods=['GET', 'POST']) @access.owner(models.User, 'user_email') def token_create(user_email): user_email = user_email or flask_login.current_user.email diff --git a/core/admin/mailu/ui/views/users.py b/core/admin/mailu/ui/views/users.py index 51dfdc13..e3c03848 100644 --- a/core/admin/mailu/ui/views/users.py +++ b/core/admin/mailu/ui/views/users.py @@ -7,7 +7,6 @@ import flask_login import wtforms import wtforms_components - @ui.route('/user/list/', methods=['GET']) @access.domain_admin(models.Domain, 'domain_name') def user_list(domain_name): @@ -44,7 +43,7 @@ def user_create(domain_name): domain=domain, form=form) -@ui.route('/user/edit/', methods=['GET', 'POST']) +@ui.route('/user/edit/', methods=['GET', 'POST']) @access.domain_admin(models.User, 'user_email') def user_edit(user_email): user = models.User.query.get(user_email) or flask.abort(404) @@ -72,7 +71,7 @@ def user_edit(user_email): domain=user.domain, max_quota_bytes=max_quota_bytes) -@ui.route('/user/delete/', methods=['GET', 'POST']) +@ui.route('/user/delete/', methods=['GET', 'POST']) @access.domain_admin(models.User, 'user_email') @access.confirmation_required("delete {user_email}") def user_delete(user_email): @@ -86,15 +85,22 @@ def user_delete(user_email): @ui.route('/user/settings', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/user/usersettings/', methods=['GET', 'POST']) +@ui.route('/user/usersettings/', methods=['GET', 'POST']) @access.owner(models.User, 'user_email') def user_settings(user_email): user_email_or_current = user_email or flask_login.current_user.email user = models.User.query.get(user_email_or_current) or flask.abort(404) form = forms.UserSettingsForm(obj=user) + if isinstance(form.forward_destination.data,str): + data = form.forward_destination.data.replace(" ","").split(",") + else: + data = form.forward_destination.data + form.forward_destination.data = ", ".join(data) if form.validate_on_submit(): + 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) flask.flash('Settings updated for %s' % user) if user_email: return flask.redirect( @@ -103,7 +109,7 @@ def user_settings(user_email): @ui.route('/user/password', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/user/password/', methods=['GET', 'POST']) +@ui.route('/user/password/', methods=['GET', 'POST']) @access.owner(models.User, 'user_email') def user_password(user_email): user_email_or_current = user_email or flask_login.current_user.email @@ -123,7 +129,7 @@ def user_password(user_email): @ui.route('/user/forward', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/user/forward/', methods=['GET', 'POST']) +@ui.route('/user/forward/', methods=['GET', 'POST']) @access.owner(models.User, 'user_email') def user_forward(user_email): user_email_or_current = user_email or flask_login.current_user.email @@ -140,7 +146,7 @@ def user_forward(user_email): @ui.route('/user/reply', methods=['GET', 'POST'], defaults={'user_email': None}) -@ui.route('/user/reply/', methods=['GET', 'POST']) +@ui.route('/user/reply/', methods=['GET', 'POST']) @access.owner(models.User, 'user_email') def user_reply(user_email): user_email_or_current = user_email or flask_login.current_user.email diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index 33374111..6c55e192 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -36,7 +36,7 @@ pyOpenSSL==18.0.0 python-dateutil==2.7.5 python-editor==1.0.3 pytz==2018.7 -PyYAML==3.13 +PyYAML==4.2b4 redis==3.0.1 six==1.11.0 SQLAlchemy==1.2.13 diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 8b1a2bae..6afa8301 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -10,6 +10,7 @@ RUN apk add --no-cache certbot nginx nginx-mod-mail openssl curl \ && pip3 install idna requests watchdog COPY conf /conf +COPY static /static COPY *.py / EXPOSE 80/tcp 443/tcp 110/tcp 143/tcp 465/tcp 587/tcp 993/tcp 995/tcp 25/tcp 10025/tcp 10143/tcp diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index c90f7806..7107a351 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -38,6 +38,8 @@ http { {% if KUBERNETES_INGRESS != 'true' %} # Main HTTP server server { + # Favicon stuff + root /static; # Variables for proxifying set $admin {{ HOST_ADMIN }}; set $antispam {{ HOST_ANTISPAM }}; @@ -90,9 +92,9 @@ http { {% if WEB_WEBMAIL != '/' %} location / { {% if WEBROOT_REDIRECT %} - return 301 {{ WEBROOT_REDIRECT }}; + try_files $uri {{ WEBROOT_REDIRECT }}; {% else %} - return 404; + try_files $uri =404; {% endif %} } {% endif %} diff --git a/core/nginx/static/android-chrome-192x192.png b/core/nginx/static/android-chrome-192x192.png new file mode 100644 index 00000000..86231db9 Binary files /dev/null and b/core/nginx/static/android-chrome-192x192.png differ diff --git a/core/nginx/static/android-chrome-512x512.png b/core/nginx/static/android-chrome-512x512.png new file mode 100644 index 00000000..f029c9fd Binary files /dev/null and b/core/nginx/static/android-chrome-512x512.png differ diff --git a/core/nginx/static/apple-touch-icon.png b/core/nginx/static/apple-touch-icon.png new file mode 100644 index 00000000..b4f92f33 Binary files /dev/null and b/core/nginx/static/apple-touch-icon.png differ diff --git a/core/nginx/static/browserconfig.xml b/core/nginx/static/browserconfig.xml new file mode 100644 index 00000000..5aecc916 --- /dev/null +++ b/core/nginx/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #00aba9 + + + diff --git a/core/nginx/static/favicon-16x16.png b/core/nginx/static/favicon-16x16.png new file mode 100644 index 00000000..ff35d4b0 Binary files /dev/null and b/core/nginx/static/favicon-16x16.png differ diff --git a/core/nginx/static/favicon-32x32.png b/core/nginx/static/favicon-32x32.png new file mode 100644 index 00000000..2b6b749b Binary files /dev/null and b/core/nginx/static/favicon-32x32.png differ diff --git a/core/nginx/static/favicon.ico b/core/nginx/static/favicon.ico new file mode 100644 index 00000000..f1f3fe03 Binary files /dev/null and b/core/nginx/static/favicon.ico differ diff --git a/core/nginx/static/mstile-150x150.png b/core/nginx/static/mstile-150x150.png new file mode 100644 index 00000000..04c609f7 Binary files /dev/null and b/core/nginx/static/mstile-150x150.png differ diff --git a/core/nginx/static/safari-pinned-tab.svg b/core/nginx/static/safari-pinned-tab.svg new file mode 100644 index 00000000..43cea812 --- /dev/null +++ b/core/nginx/static/safari-pinned-tab.svg @@ -0,0 +1,25 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/core/nginx/static/site.webmanifest b/core/nginx/static/site.webmanifest new file mode 100644 index 00000000..b20abb7c --- /dev/null +++ b/core/nginx/static/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/docs/configuration.rst b/docs/configuration.rst index 2f44b293..b8f2a90c 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -71,6 +71,14 @@ Web settings The ``WEB_ADMIN`` contains the path to the main admin interface, while ``WEB_WEBMAIL`` contains the path to the Web email client. +The ``WEBROOT_REDIRECT`` redirects all non-found queries to the set path. +An empty ``WEBROOT_REDIRECT`` value disables redirecting and enables classic +behavior of a 404 result when not found. +All three options need a leading slash (``/``) to work. + + .. note:: ``WEBROOT_REDIRECT`` has to point to a valid path on the webserver. + This means it cannot point to any services which are not enabled. + For example, don't point it to ``/webmail`` when ``WEBMAIL=none`` Both ``SITENAME`` and ``WEBSITE`` are customization options for the panel menu in the admin interface, while ``SITENAME`` is a customization option for diff --git a/docs/demo.rst b/docs/demo.rst index 9415e670..fb66f2ff 100644 --- a/docs/demo.rst +++ b/docs/demo.rst @@ -18,7 +18,7 @@ Functionality - The server is reset every day at 3am, UTC. - You can send mail from any client to the server. - However, the stmp server is made incapable of relaying the e-mail to the destination server. + However, the SMTP server is made incapable of relaying the e-mail to the destination server. As such, the mail will never arrive. This is to prevent abuse of the server. - The server is capable of receiving mail for any configured domains. - The server exposes IMAP, POP3 and SMTP as usual for connection with mail clients such as Thunderbird. diff --git a/docs/faq.rst b/docs/faq.rst index 395b739c..2669d9d1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -89,6 +89,51 @@ our ongoing `project management`_ discussion issue. Deployment related ------------------ +What is the difference between DOMAIN and HOSTNAMES? +```````````````````````````````````````````````````` + +Similar questions: + +- Changing domain doesn't work +- Do I need a certificate for ``DOMAIN``? + +``DOMAIN`` is the main mail domain. Aka, server identification for outgoing mail. DMARC reports point to ``POSTMASTER`` @ ``DOMAIN``. +These are really the only things it is used for. You don't need a cert for ``DOMAIN``, as it is a mail domain only and not used as host in any sense. +However, it is usual that ``DOMAIN`` gets setup as one of the many mail domains. None of the mail domains ever need a certificate. +TLS certificates work on host connection level only. + +``HOSTNAMES`` however, can be used to connect to the server. All host names supplied in this variable will need a certificate. When ``TLS_FLAVOR=letsencrypt`` is set, +a certificate is requested automatically for all those domains. + +So when you have something like this: + +.. code-block:: bash + + DOMAIN=example.com + POSTMASTER=me + HOSTNAMES=mail.example.com,mail.foo.com,bar.com + TLS_FLAVOR=letsencrypt + +- You'll end up with a DMARC address to ``me@example.com``. +- Server identifies itself as the SMTP server of ``@example.com`` when sending mail. Make sure your reverse DNS hostname is part of that domain! +- Your server will have certificates for the 3 hostnames. You will need to create ``A`` and ``AAAA`` records for those names, + pointing to the IP addresses of your server. +- The admin interface generates ``MX`` and ``SPF`` examples which point to the first entry of ``HOSTNAMES`` but these are only examples. + You can modify them to use any other ``HOSTNAMES`` entry. + +You're mail service will be reachable for IMAP, POP3, SMTP and Webmail at the addresses: + +- mail.example.com +- mail.foo.com +- bar.com + +.. note:: + + In this case ``example.com`` is not reachable as a host and will not have a certificate. + It can be used as a mail domain if MX is setup to point to one of the ``HOSTNAMES``. However, it is possible to include ``example.com`` in ``HOSTNAMES``. + +*Issue reference:* `742`_, `747`_. + How does Mailu scale up? ```````````````````````` @@ -123,6 +168,16 @@ For **service** HA, please see: `How does Mailu scale up?`_ .. _`spam magnet`: https://blog.zensoftware.co.uk/2012/07/02/why-we-tend-to-recommend-not-having-a-secondary-mx-these-days/ +Does Mailu run on Rancher? +`````````````````````````` + +There is a rancher catalog for Mailu in the `Mailu/Rancher`_ repository. The user group for Rancher is small, +so we cannot promise any support on this when you're heading into trouble. See the repository README for more details. + +*Issue reference:* `125`_. + +.. _`Mailu/Rancher`: https://github.com/Mailu/Rancher + Can I run Mailu without host iptables? `````````````````````````````````````` @@ -138,24 +193,67 @@ For that reason we do **not** support deployment on Docker hosts without iptable How can I override settings? ```````````````````````````` -Postfix, dovecot and Rspamd support overriding configuration files. Override files belong in +Postfix, Dovecot, Nginx and Rspamd support overriding configuration files. Override files belong in ``$ROOT/overrides``. Please refer to the official documentation of those programs for the correct syntax. The following file names will be taken as override configuration: - `Postfix`_ - ``postfix.cf``; - `Dovecot`_ - ``dovecot.conf``; +- `Nginx`_ - All ``*.conf`` files in the ``nginx`` sub-directory. - `Rspamd`_ - All files in the ``rspamd`` sub-directory. +*Issue reference:* `206`_. + +I want to integrate Nextcloud with Mailu +```````````````````````````````````````` + +First of all you have to install dependencies required to authenticate users via imap in Nextcloud + +.. code-block:: bash + + apt-get update \ + && apt-get install -y libc-client-dev libkrb5-dev \ + && rm -rf /var/lib/apt/lists/* \ + && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ + && docker-php-ext-install imap + +Next, you have to enable External user support from Nextcloud Apps interface + +In the end you need to configure additional user backends in Nextcloud’s configuration config/config.php using the following syntax: + +.. code-block:: bash + + array( + array( + 'class' => 'OC_User_IMAP', + 'arguments' => array( + '{imap.example.com:993/imap/ssl}', 'example.com' + ), + ), + ), + +If a domain name (e.g. example.com) is specified, then this makes sure that only users from this domain will be allowed to login. +After successfull login the domain part will be striped and the rest used as username in NextCloud. e.g. 'username@example.com' will be 'username' in NextCloud. + +*Issue reference:* `575`_. + .. _`Postfix`: http://www.postfix.org/postconf.5.html .. _`Dovecot`: https://wiki.dovecot.org/ConfigFile -.. _`Rspamd`: https://www.rspamd.com/doc/configuration/index.html +.. _`NGINX`: https://nginx.org/en/docs/ +.. _`Rspamd`: https://www.rspamd.com/doc/configuration/index.html .. _`Docker swarm howto`: https://github.com/Mailu/Mailu/tree/master/docs/swarm/master +.. _`125`: https://github.com/Mailu/Mailu/issues/125 .. _`165`: https://github.com/Mailu/Mailu/issues/165 .. _`177`: https://github.com/Mailu/Mailu/issues/177 .. _`332`: https://github.com/Mailu/Mailu/issues/332 +.. _`742`: https://github.com/Mailu/Mailu/issues/742 +.. _`747`: https://github.com/Mailu/Mailu/issues/747 .. _`520`: https://github.com/Mailu/Mailu/issues/520 .. _`591`: https://github.com/Mailu/Mailu/issues/591 +.. _`575`: https://github.com/Mailu/Mailu/issues/575 Technical issues ---------------- @@ -241,8 +339,18 @@ See also :ref:`external_certs`. *Issue reference:* `426`_, `615`_. +How do I activate DKIM and DMARC? +````````````````````````````````` +Go into the Domain Panel and choose the Domain you want to enable DKIM for. +Click the first icon on the left side (domain details). +Now click on the top right on the *"Regenerate Keys"* Button. +This will generate the DKIM and DMARC entries for you. + +*Issue reference:* `102`_. + Do you support Fail2Ban? ```````````````````````` + Fail2Ban is not included in Mailu. Fail2Ban needs to modify the host's IP tables in order to ban the addresses. We consider such a program should be run on the host system and not inside a container. The ``front`` container does use authentication rate limiting to slow @@ -265,12 +373,49 @@ spam filter weight settings. *Issue reference:* `503`_. +rspamd: DNS query blocked on multi.uribl.com +```````````````````````````````````````````` + +This usually relates to the DNS server you are using. Most of the public servers block this query or there is a rate limit. +In order to solve this, you most probably are better off using a root DNS resolver, such as `unbound`_. This can be done in multiple ways: + +- Use the *Mailu/unbound* container. This is an optional include when generating the ``docker-compose.yml`` file with the setup utility. +- Setup unbound on the host and make sure the host's ``/etc/resolve.conf`` points to local host. + Docker will then forward all external DNS requests to the local server. +- Set up an external DNS server with root resolving capabilities. + +In any case, using a dedicated DNS server will improve the performance of your mail server. + +*Issue reference:* `206`_, `554`_, `681`_. + +Is there a way to support more (older) ciphers? +``````````````````````````````````````````````` + +See `How can I override settings?`_ . +You will need to add the protocols you wish to support in an override for the ``front`` container (Nginx). + +.. code-block:: bash + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers ; + +We **strongly** advice against downgrading the TLS version and ciphers! + +*Issue reference:* `363`_, `698`_. + .. _`troubleshooting tag`: https://github.com/Mailu/Mailu/issues?utf8=%E2%9C%93&q=label%3Afaq%2Ftroubleshooting .. _`85`: https://github.com/Mailu/Mailu/issues/85 +.. _`102`: https://github.com/Mailu/Mailu/issues/102 .. _`116`: https://github.com/Mailu/Mailu/issues/116 .. _`171`: https://github.com/Mailu/Mailu/issues/171 +.. _`206`: https://github.com/Mailu/Mailu/issues/206 +.. _`363`: https://github.com/Mailu/Mailu/issues/363 .. _`426`: https://github.com/Mailu/Mailu/issues/426 .. _`503`: https://github.com/Mailu/Mailu/issues/503 +.. _`554`: https://github.com/Mailu/Mailu/issues/554 .. _`584`: https://github.com/Mailu/Mailu/issues/584 .. _`592`: https://github.com/Mailu/Mailu/issues/592 .. _`615`: https://github.com/Mailu/Mailu/issues/615 +.. _`681`: https://github.com/Mailu/Mailu/pull/681 +.. _`698`: https://github.com/Mailu/Mailu/issues/698 +.. _`unbound`: https://nlnetlabs.nl/projects/unbound/about/ diff --git a/setup/docker-compose.yml b/setup/docker-compose.yml index 7c31d2cd..6d14153a 100644 --- a/setup/docker-compose.yml +++ b/setup/docker-compose.yml @@ -5,17 +5,21 @@ version: '3.6' services: redis: image: redis:alpine + networks: + - default setup_master: image: mailu/setup:master networks: - web + - default env_file: .env environment: this_version: "master" labels: - traefik.enable=true - traefik.port=80 + - traefik.docker.network=web - traefik.main.frontend.rule=Host:${ADDRESS};PathPrefix:/master/ depends_on: - redis @@ -24,12 +28,14 @@ services: image: mailu/setup:${RELEASE} networks: - web + - default env_file: .env environment: this_version: ${RELEASE} labels: - traefik.enable=true - traefik.port=80 + - traefik.docker.network=web - traefik.root.frontend.redirect.regex=.* - traefik.root.frontend.redirect.replacement=/${RELEASE}/ - traefik.root.frontend.rule=Host:${ADDRESS};PathPrefix:/ @@ -40,3 +46,5 @@ services: networks: web: external: true + default: + external: false diff --git a/setup/flavors/compose/setup.html b/setup/flavors/compose/setup.html index 0487c98f..46369577 100644 --- a/setup/flavors/compose/setup.html +++ b/setup/flavors/compose/setup.html @@ -11,8 +11,8 @@ in a project directory. First create your project directory.

    to read and check the configuration variables generated by the wizard.

    cd {{ root }}
    -curl {{ url_for('.file', uid=uid, filepath='docker-compose.yml', _external=True) }} > docker-compose.yml
    -curl {{ url_for('.file', uid=uid, filepath='mailu.env', _external=True) }} > mailu.env
    +wget {{ url_for('.file', uid=uid, filepath='docker-compose.yml', _external=True) }}
    +wget {{ url_for('.file', uid=uid, filepath='mailu.env', _external=True) }}
     
    {% endcall %} diff --git a/setup/flavors/stack/setup.html b/setup/flavors/stack/setup.html index 329a2cba..81470b5b 100644 --- a/setup/flavors/stack/setup.html +++ b/setup/flavors/stack/setup.html @@ -11,8 +11,8 @@ in a project directory. First create your project directory.

    to read and check the configuration variables generated by the wizard.

    cd {{ root }}
    -curl {{ url_for('.file', uid=uid, filepath='docker-compose.yml', _external=True) }} > docker-compose.yml
    -curl {{ url_for('.file', uid=uid, filepath='mailu.env', _external=True) }} > mailu.env
    +wget {{ url_for('.file', uid=uid, filepath='docker-compose.yml', _external=True) }}
    +wget {{ url_for('.file', uid=uid, filepath='mailu.env', _external=True) }}
     
    {% endcall %} diff --git a/setup/server.py b/setup/server.py index fbe8a87f..fea27ead 100644 --- a/setup/server.py +++ b/setup/server.py @@ -11,7 +11,9 @@ import ipaddress import hashlib -app = flask.Flask(__name__) +version = os.getenv("this_version") +static_url_path = "/" + version + "/static" +app = flask.Flask(__name__, static_url_path=static_url_path) flask_bootstrap.Bootstrap(app) db = redis.StrictRedis(host='redis', port=6379, db=0) @@ -41,29 +43,37 @@ def build_app(path): def app_context(): return dict(versions=os.getenv("VERSIONS","master").split(',')) - version = os.getenv("this_version") - - bp = flask.Blueprint(version, __name__) - bp.jinja_loader = jinja2.ChoiceLoader([ + prefix_bp = flask.Blueprint(version, __name__) + prefix_bp.jinja_loader = jinja2.ChoiceLoader([ jinja2.FileSystemLoader(os.path.join(path, "templates")), jinja2.FileSystemLoader(os.path.join(path, "flavors")) ]) - @bp.context_processor + root_bp = flask.Blueprint("root", __name__) + root_bp.jinja_loader = jinja2.ChoiceLoader([ + jinja2.FileSystemLoader(os.path.join(path, "templates")), + jinja2.FileSystemLoader(os.path.join(path, "flavors")) + ]) + + @prefix_bp.context_processor + @root_bp.context_processor def bp_context(version=version): return dict(version=version) - @bp.route("/") + @prefix_bp.route("/") + @root_bp.route("/") def wizard(): return flask.render_template('wizard.html') - @bp.route("/submit_flavor", methods=["POST"]) + @prefix_bp.route("/submit_flavor", methods=["POST"]) + @root_bp.route("/submit_flavor", methods=["POST"]) def submit_flavor(): data = flask.request.form.copy() steps = sorted(os.listdir(os.path.join(path, "templates", "steps", data["flavor"]))) return flask.render_template('wizard.html', flavor=data["flavor"], steps=steps) - @bp.route("/submit", methods=["POST"]) + @prefix_bp.route("/submit", methods=["POST"]) + @root_bp.route("/submit", methods=["POST"]) def submit(): data = flask.request.form.copy() data['uid'] = str(uuid.uuid4()) @@ -71,14 +81,16 @@ def build_app(path): db.set(data['uid'], json.dumps(data)) return flask.redirect(flask.url_for('.setup', uid=data['uid'])) - @bp.route("/setup/", methods=["GET"]) + @prefix_bp.route("/setup/", methods=["GET"]) + @root_bp.route("/setup/", methods=["GET"]) def setup(uid): data = json.loads(db.get(uid)) flavor = data.get("flavor", "compose") rendered = render_flavor(flavor, "setup.html", data) return flask.render_template("setup.html", contents=rendered) - @bp.route("/file//", methods=["GET"]) + @prefix_bp.route("/file//", methods=["GET"]) + @root_bp.route("/file//", methods=["GET"]) def file(uid, filepath): data = json.loads(db.get(uid)) flavor = data.get("flavor", "compose") @@ -87,7 +99,8 @@ def build_app(path): mimetype="application/text" ) - app.register_blueprint(bp, url_prefix="/{}".format(version)) + app.register_blueprint(prefix_bp, url_prefix="/{}".format(version)) + app.register_blueprint(root_bp) if __name__ == "__main__": diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index 92479489..224fe457 100644 --- a/webmails/rainloop/Dockerfile +++ b/webmails/rainloop/Dockerfile @@ -2,7 +2,8 @@ FROM php:7.2-apache #Shared layer between rainloop and roundcube RUN apt-get update && apt-get install -y \ python3 curl \ - && rm -rf /var/lib/apt/lists + && rm -rf /var/lib/apt/lists \ + && echo "ServerSignature Off" >> /etc/apache2/apache2.conf ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.12.1/rainloop-community-1.12.1.zip diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index 00b843b2..1c5d82c4 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -2,7 +2,8 @@ FROM php:7.2-apache #Shared layer between rainloop and roundcube RUN apt-get update && apt-get install -y \ python3 curl \ - && rm -rf /var/lib/apt/lists + && rm -rf /var/lib/apt/lists \ + && echo "ServerSignature Off" >> /etc/apache2/apache2.conf ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.3.8/roundcubemail-1.3.8-complete.tar.gz