From c2d45a47fe96b5093f5f2ee3f34ec41c8dfb93de Mon Sep 17 00:00:00 2001 From: Dario Ernst Date: Thu, 27 Dec 2018 15:36:24 +0100 Subject: [PATCH 01/25] Attempt stripping recipient delimiter from localpart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since postfix now asks us for the complete email over podop, which includes the recipient-delimiter-and-what-follows not stripped, we need to attempt to find both the verbatim localpart, as well as the localpart stripped of the delimited part …. Fixes #755 --- core/admin/mailu/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index a3fc15a4..2b70e408 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -260,10 +260,19 @@ class Email(object): @classmethod def resolve_destination(cls, localpart, domain_name, ignore_forward_keep=False): + localpart_stripped = None + if os.environ.get('RECIPIENT_DELIMITER') in localpart: + localpart_stripped = localpart.rsplit(os.environ.get('RECIPIENT_DELIMITER'), 1)[0] + alias = Alias.resolve(localpart, domain_name) + if not alias and localpart_stripped: + alias = Alias.resolve(localpart_stripped, domain_name) if alias: return alias.destination + user = User.query.get('{}@{}'.format(localpart, domain_name)) + if not user and localpart_stripped: + user = User.query.get('{}@{}'.format(localpart_stripped, domain_name)) if user: if user.forward_enabled: destination = user.forward_destination From ce7105c3ad70e7fad5c7b0354301e359eb4dc632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Wed, 2 Jan 2019 20:33:42 +0200 Subject: [PATCH 02/25] Create PULL_REQUEST_TEMPLATE.md --- PULL_REQUEST_TEMPLATE.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..84a5cb8f --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## What type of PR? + +(Feature, enhancement, bug-fix, documentation) + +## What does this PR do? + + + +## 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. From 4e8f899b28e2152843f00eae12bd73da1eb20fc5 Mon Sep 17 00:00:00 2001 From: Ionut Filip Date: Fri, 4 Jan 2019 11:19:43 +0200 Subject: [PATCH 03/25] Updated faq with nextcloud integration --- docs/faq.rst | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index cf1895ea..a48c14c4 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -204,6 +204,41 @@ correct syntax. The following file names will be taken as override configuration *Issue reference:* `206`_. +I want to integrate Nextcloud with Mailu +```````````````````````````````````````` + +First of all you have to to install next dependencies 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 .. _`NGINX`: https://nginx.org/en/docs/ @@ -218,6 +253,7 @@ correct syntax. The following file names will be taken as override configuration .. _`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 ---------------- @@ -304,7 +340,7 @@ 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. @@ -367,7 +403,6 @@ 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 From 0764c81a575154dd4ad97dbebaef840418ff5286 Mon Sep 17 00:00:00 2001 From: Ionut Filip Date: Fri, 4 Jan 2019 12:40:02 +0200 Subject: [PATCH 04/25] Fixed typo --- docs/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.rst b/docs/faq.rst index a48c14c4..2669d9d1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -207,7 +207,7 @@ correct syntax. The following file names will be taken as override configuration I want to integrate Nextcloud with Mailu ```````````````````````````````````````` -First of all you have to to install next dependencies in Nextcloud +First of all you have to install dependencies required to authenticate users via imap in Nextcloud .. code-block:: bash From 3c7bf58211e1ddbf97911ee3fce78f962b8518cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 4 Jan 2019 21:52:43 +0200 Subject: [PATCH 05/25] Upgrade PyYAML CVE-2017-18342 Vulnerable versions: < 4.2b1 Patched version: 4.2b1 In PyYAML before 4.1, the yaml.load() API could execute arbitrary code. In other words, yaml.safe_load is not used. --- core/admin/requirements-prod.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index a538c023..5f59bb9a 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -34,7 +34,7 @@ pyOpenSSL==18.0.0 python-dateutil==2.7.5 python-editor==1.0.3 pytz==2018.7 -PyYAML==3.13 +PyYAML==4.2b1 redis==3.0.1 six==1.11.0 SQLAlchemy==1.2.13 From 284d54190ae0678aa67e942299237eeed3e35aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 6 Jan 2019 14:40:29 +0200 Subject: [PATCH 06/25] Upgrade PyYAML to 4.2b4 --- core/admin/requirements-prod.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index 5f59bb9a..3679b63f 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -34,7 +34,7 @@ pyOpenSSL==18.0.0 python-dateutil==2.7.5 python-editor==1.0.3 pytz==2018.7 -PyYAML==4.2b1 +PyYAML==4.2b4 redis==3.0.1 six==1.11.0 SQLAlchemy==1.2.13 From 4f93e09028d0d5b7b8751a0059b97f9938994d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 6 Jan 2019 15:49:40 +0200 Subject: [PATCH 07/25] Implement favicon package Credit to: - https://stackoverflow.com/a/19590415/1816774 - https://realfavicongenerator.net/ --- core/admin/mailu/ui/templates/base.html | 7 ++++++ core/nginx/Dockerfile | 1 + core/nginx/conf/nginx.conf | 6 +++-- core/nginx/static/android-chrome-192x192.png | Bin 0 -> 6274 bytes core/nginx/static/android-chrome-512x512.png | Bin 0 -> 18169 bytes core/nginx/static/apple-touch-icon.png | Bin 0 -> 5802 bytes core/nginx/static/browserconfig.xml | 9 +++++++ core/nginx/static/favicon-16x16.png | Bin 0 -> 978 bytes core/nginx/static/favicon-32x32.png | Bin 0 -> 1523 bytes core/nginx/static/favicon.ico | Bin 0 -> 15086 bytes core/nginx/static/mstile-150x150.png | Bin 0 -> 4788 bytes core/nginx/static/safari-pinned-tab.svg | 25 +++++++++++++++++++ core/nginx/static/site.webmanifest | 19 ++++++++++++++ 13 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 core/nginx/static/android-chrome-192x192.png create mode 100644 core/nginx/static/android-chrome-512x512.png create mode 100644 core/nginx/static/apple-touch-icon.png create mode 100644 core/nginx/static/browserconfig.xml create mode 100644 core/nginx/static/favicon-16x16.png create mode 100644 core/nginx/static/favicon-32x32.png create mode 100644 core/nginx/static/favicon.ico create mode 100644 core/nginx/static/mstile-150x150.png create mode 100644 core/nginx/static/safari-pinned-tab.svg create mode 100644 core/nginx/static/site.webmanifest diff --git a/core/admin/mailu/ui/templates/base.html b/core/admin/mailu/ui/templates/base.html index 73fa7aa7..c27776e2 100644 --- a/core/admin/mailu/ui/templates/base.html +++ b/core/admin/mailu/ui/templates/base.html @@ -13,6 +13,13 @@ {% block head %} {{super()}} + + + + + + + {% block scripts %} {{super()}} 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..1733f8c4 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 $uri/ {{ WEBROOT_REDIRECT }}; {% else %} - return 404; + try_files $uri $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 0000000000000000000000000000000000000000..86231db9042b8cf7191d469d5c1c39c164ba9514 GIT binary patch literal 6274 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ff35d4b0a7618b3b4df63d27c6d0793ced5735d1 GIT binary patch literal 978 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>tE{G zuNM10z5e;H_|i9{y&w5Ey+82!ch!||46EPi?f&R_^t1k+k9WWPee&(^oiBg&_IzYs z_fCAv2cS2#c74>{{ZVt*N1qO(-@s5cED7=hMiHyig1g`5i*~I_cV4F0NO=9DQ$?w^{RzCgXS-e!`F;FvOlDE5ywI$z|93Y3i#M9T6 z{V^jOpNQC$*T3EYh3C<#g|S12gTPs_|nRVb+}NL9!zC`o2usF?Hk6AwpWn1;qF|I=qYp9V27 zD|725a|bP0l+XkK6l2B> literal 0 HcmV?d00001 diff --git a/core/nginx/static/favicon-32x32.png b/core/nginx/static/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2b6b749b92a5d0f19c470f28ae0cc8eb70efec23 GIT binary patch literal 1523 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 biIpnwO^_#O%S1baVFIu;vo$4~T)X!l@xsf9 literal 0 HcmV?d00001 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" +} From 5bdeee7b49ba32f808a380185ed109b503ad8790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 6 Jan 2019 16:02:26 +0200 Subject: [PATCH 08/25] Example for related and autoclose issues --- PULL_REQUEST_TEMPLATE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 84a5cb8f..856fa5a0 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,9 @@ ## 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. From 5df63c6d7f79a60a2dd0d4b79f2a0c78c2ef1ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Sun, 6 Jan 2019 16:16:06 +0200 Subject: [PATCH 09/25] Favicon changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b51220..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)) From 4b0601cb64fd715860f35878e2e0bd588dd14104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 7 Jan 2019 12:23:18 +0200 Subject: [PATCH 10/25] Add WEBROOT_REDIRECT documentation Closes #802 --- docs/configuration.rst | 8 ++++++++ 1 file changed, 8 insertions(+) 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 From 5636e7f5a772b09b50e35e69327129384a15175d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 7 Jan 2019 14:08:00 +0200 Subject: [PATCH 11/25] Remove to avoid matching webroot --- core/nginx/conf/nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 1733f8c4..7107a351 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -92,9 +92,9 @@ http { {% if WEB_WEBMAIL != '/' %} location / { {% if WEBROOT_REDIRECT %} - try_files $uri $uri/ {{ WEBROOT_REDIRECT }}; + try_files $uri {{ WEBROOT_REDIRECT }}; {% else %} - try_files $uri $uri/ =404; + try_files $uri =404; {% endif %} } {% endif %} From b9313488dd3cf40fc93553ff847699c30c00972e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Mon, 7 Jan 2019 23:49:10 +0200 Subject: [PATCH 12/25] Add logging for tenacity.retry In the process we found that the previous way of tenacity syntax caused it not to honor any args. In this commit we've refactored to use the @decorator syntax, in which tenacity seems to behave better. --- core/dovecot/start.py | 18 ++++++++++++++++-- core/postfix/start.py | 20 +++++++++++++++++--- services/rspamd/start.py | 19 +++++++++++++++++-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 8bf66efd..2fdc7361 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -6,14 +6,17 @@ import socket import glob import multiprocessing import tenacity +import logging as log +import sys from tenacity import retry from podop import run_server +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") def start_podop(): os.setuid(8) - run_server(3 if "DEBUG" in os.environ else 0, "dovecot", "/tmp/podop.socket", [ + run_server(0, "dovecot", "/tmp/podop.socket", [ ("quota", "url", "http://admin/internal/dovecot/§"), ("auth", "url", "http://admin/internal/dovecot/§"), ("sieve", "url", "http://admin/internal/dovecot/§"), @@ -21,8 +24,19 @@ def start_podop(): convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +@retry( + stop=tenacity.stop_after_attempt(100), + wait=tenacity.wait_random(min=2, max=5), + before=tenacity.before_log(log.getLogger("tenacity.retry"), log.DEBUG), + before_sleep=tenacity.before_sleep_log(log.getLogger("tenacity.retry"), log.INFO), + after=tenacity.after_log(log.getLogger("tenacity.retry"), log.DEBUG) + ) +def resolve(hostname): + logger = log.getLogger("resolve()") + logger.info(hostname) + return socket.gethostbyname(hostname) + # Actual startup script -resolve = retry(socket.gethostbyname, stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) os.environ["REDIS_ADDRESS"] = resolve(os.environ.get("REDIS_ADDRESS", "redis")) if os.environ["WEBMAIL"] != "none": diff --git a/core/postfix/start.py b/core/postfix/start.py index 86e9a827..c41a2f4c 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -7,14 +7,18 @@ import glob import shutil import tenacity import multiprocessing +import logging as log +import sys from tenacity import retry from podop import run_server +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") def start_podop(): os.setuid(100) - run_server(3 if "DEBUG" in os.environ else 0, "postfix", "/tmp/podop.socket", [ + # TODO: Remove verbosity setting from Podop? + run_server(0, "postfix", "/tmp/podop.socket", [ ("transport", "url", "http://admin/internal/postfix/transport/§"), ("alias", "url", "http://admin/internal/postfix/alias/§"), ("domain", "url", "http://admin/internal/postfix/domain/§"), @@ -25,9 +29,19 @@ def start_podop(): convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) -# Actual startup script -resolve = retry(socket.gethostbyname, stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) +@retry( + stop=tenacity.stop_after_attempt(100), + wait=tenacity.wait_random(min=2, max=5), + before=tenacity.before_log(log.getLogger("tenacity.retry"), log.DEBUG), + before_sleep=tenacity.before_sleep_log(log.getLogger("tenacity.retry"), log.INFO), + after=tenacity.after_log(log.getLogger("tenacity.retry"), log.DEBUG) + ) +def resolve(hostname): + logger = log.getLogger("resolve()") + logger.info(hostname) + return socket.gethostbyname(hostname) +# Actual startup script os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) os.environ["HOST_ANTISPAM"] = os.environ.get("HOST_ANTISPAM", "antispam:11332") os.environ["HOST_LMTP"] = os.environ.get("HOST_LMTP", "imap:2525") diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 0b3c48a8..cc4efa85 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -5,13 +5,28 @@ import os import socket import glob import tenacity +import logging as log +import sys + from tenacity import retry +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") + convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) -# Actual startup script -resolve = retry(socket.gethostbyname, stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) +@retry( + stop=tenacity.stop_after_attempt(100), + wait=tenacity.wait_random(min=2, max=5), + before=tenacity.before_log(log.getLogger("tenacity.retry"), log.DEBUG), + before_sleep=tenacity.before_sleep_log(log.getLogger("tenacity.retry"), log.INFO), + after=tenacity.after_log(log.getLogger("tenacity.retry"), log.DEBUG) + ) +def resolve(hostname): + logger = log.getLogger("resolve()") + logger.info(hostname) + return socket.gethostbyname(hostname) +# Actual startup script os.environ["FRONT_ADDRESS"] = resolve(os.environ.get("FRONT_ADDRESS", "front")) if "HOST_REDIS" not in os.environ: os.environ["HOST_REDIS"] = "redis" From e994e94512be8f78aa9528ad7549d11047119744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 8 Jan 2019 00:35:52 +0200 Subject: [PATCH 13/25] Implement logging in clamav start script --- optional/clamav/start.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/optional/clamav/start.py b/optional/clamav/start.py index d4701d2d..19f91bcf 100755 --- a/optional/clamav/start.py +++ b/optional/clamav/start.py @@ -1,12 +1,21 @@ #!/usr/bin/python3 import os +import logging as log +import sys + +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +logger=log.getLogger(__name__) # Bootstrap the database if clamav is running for the first time -os.system("[ -f /data/main.cvd ] || freshclam") +if not os.path.isfile("/data/main.cvd"): + logger.info("Starting primary virus DB download") + os.system("freshclam") # Run the update daemon +logger.info("Starting the update daemon") os.system("freshclam -d -c 6") # Run clamav +logger.info("Starting clamav") os.system("clamd") From b04a9d1c2833511f653eaf479f2e44b1cee750dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 8 Jan 2019 00:38:06 +0200 Subject: [PATCH 14/25] Implement debug logging for template rendering --- core/dovecot/start.py | 5 ++++- core/nginx/config.py | 11 +++++++++-- core/postfix/start.py | 5 ++++- services/rspamd/start.py | 5 ++++- services/unbound/start.py | 10 +++++++++- webmails/rainloop/start.py | 9 ++++++++- webmails/roundcube/start.py | 11 +++++++++-- 7 files changed, 47 insertions(+), 9 deletions(-) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 2fdc7361..68043cd1 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -22,7 +22,10 @@ def start_podop(): ("sieve", "url", "http://admin/internal/dovecot/§"), ]) -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) @retry( stop=tenacity.stop_after_attempt(100), diff --git a/core/nginx/config.py b/core/nginx/config.py index 07b7ea32..719a6450 100755 --- a/core/nginx/config.py +++ b/core/nginx/config.py @@ -2,11 +2,18 @@ import jinja2 import os - -convert = lambda src, dst, args: open(dst, "w").write(jinja2.Template(open(src).read()).render(**args)) +import logging as log +import sys args = os.environ.copy() +log.basicConfig(stream=sys.stderr, level=args["LOG_LEVEL"] if "LOG_LEVEL" in args else "WARN") + +def convert(src, dst, args): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**args)) + # Get the first DNS server with open("/etc/resolv.conf") as handle: content = handle.read().split() diff --git a/core/postfix/start.py b/core/postfix/start.py index c41a2f4c..3214e077 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -27,7 +27,10 @@ def start_podop(): ("senderlogin", "url", "http://admin/internal/postfix/sender/login/§") ]) -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) @retry( stop=tenacity.stop_after_attempt(100), diff --git a/services/rspamd/start.py b/services/rspamd/start.py index cc4efa85..8819407f 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -12,7 +12,10 @@ from tenacity import retry log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) @retry( stop=tenacity.stop_after_attempt(100), diff --git a/services/unbound/start.py b/services/unbound/start.py index 6f494762..198934c6 100755 --- a/services/unbound/start.py +++ b/services/unbound/start.py @@ -2,8 +2,16 @@ import jinja2 import os +import logging as log +import sys + +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) convert("/unbound.conf", "/etc/unbound/unbound.conf") os.execv("/usr/sbin/unbound", ["-c /etc/unbound/unbound.conf"]) diff --git a/webmails/rainloop/start.py b/webmails/rainloop/start.py index 4c116e09..3b4ac3da 100755 --- a/webmails/rainloop/start.py +++ b/webmails/rainloop/start.py @@ -3,8 +3,15 @@ import jinja2 import os import shutil +import logging as log +import sys -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) # Actual startup script os.environ["FRONT_ADDRESS"] = os.environ.get("FRONT_ADDRESS", "front") diff --git a/webmails/roundcube/start.py b/webmails/roundcube/start.py index 3a0bd0bc..ccb5faf5 100755 --- a/webmails/roundcube/start.py +++ b/webmails/roundcube/start.py @@ -2,8 +2,15 @@ import os import jinja2 +import logging as log +import sys -convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") + +def convert(src, dst): + logger = log.getLogger("convert()") + logger.debug("Source: %s, Destination: %s", src, dst) + open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ)) os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576)) @@ -14,4 +21,4 @@ os.system("mkdir -p /data/gpg") os.system("chown -R www-data:www-data /data") # Run apache -os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"]) \ No newline at end of file +os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"]) From 7d01bb2a4d78a75a29b6cdb00cd917ae4ff0cfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 8 Jan 2019 00:58:01 +0200 Subject: [PATCH 15/25] LOG_LEVEL docs and changelog entry --- CHANGELOG.md | 1 + core/dovecot/start.py | 2 +- core/nginx/config.py | 2 +- core/postfix/start.py | 2 +- docs/compose/.env | 3 +++ docs/configuration.rst | 7 +++++++ optional/clamav/start.py | 2 +- services/rspamd/start.py | 2 +- services/unbound/start.py | 2 +- setup/flavors/compose/mailu.env | 3 +++ webmails/rainloop/start.py | 2 +- webmails/roundcube/start.py | 2 +- 12 files changed, 22 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b51220..bca7c66b 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: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to controll verbosity ([#588](https://github.com/Mailu/Mailu/issues/588)) - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 68043cd1..b4289b76 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -12,7 +12,7 @@ import sys from tenacity import retry from podop import run_server -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") def start_podop(): os.setuid(8) diff --git a/core/nginx/config.py b/core/nginx/config.py index 719a6450..528a3247 100755 --- a/core/nginx/config.py +++ b/core/nginx/config.py @@ -7,7 +7,7 @@ import sys args = os.environ.copy() -log.basicConfig(stream=sys.stderr, level=args["LOG_LEVEL"] if "LOG_LEVEL" in args else "WARN") +log.basicConfig(stream=sys.stderr, level=args["LOG_LEVEL"] if "LOG_LEVEL" in args else "WARNING") def convert(src, dst, args): logger = log.getLogger("convert()") diff --git a/core/postfix/start.py b/core/postfix/start.py index 3214e077..5a8c2968 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -13,7 +13,7 @@ import sys from tenacity import retry from podop import run_server -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") def start_podop(): os.setuid(100) diff --git a/docs/compose/.env b/docs/compose/.env index 836e9dbf..f65c2f01 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -151,3 +151,6 @@ REAL_IP_FROM= # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) REJECT_UNLISTED_RECIPIENT= + +# Log level threshold in start.py (value: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET) +LOG_LEVEL=WARNING diff --git a/docs/configuration.rst b/docs/configuration.rst index 2f44b293..ecb42fe5 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -83,6 +83,13 @@ The ``PASSWORD_SCHEME`` is the password encryption scheme. You should use the default value, unless you are importing password from a separate system and want to keep using the old password encryption scheme. +The ``LOG_LEVEL`` setting is used by the python start-up scripts as a logging threshold. +Log messages equal or higher than this priority will be printed. +Can be one of: CRITICAL, ERROR, WARNING, INFO, DEBUG or NOTSET. +See the `python docs`_ for more information. + +.. _`python docs`: https://docs.python.org/3.6/library/logging.html#logging-levels + Infrastructure settings ----------------------- diff --git a/optional/clamav/start.py b/optional/clamav/start.py index 19f91bcf..c08df509 100755 --- a/optional/clamav/start.py +++ b/optional/clamav/start.py @@ -4,7 +4,7 @@ import os import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") logger=log.getLogger(__name__) # Bootstrap the database if clamav is running for the first time diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 8819407f..047bfeba 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -10,7 +10,7 @@ import sys from tenacity import retry -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") def convert(src, dst): logger = log.getLogger("convert()") diff --git a/services/unbound/start.py b/services/unbound/start.py index 198934c6..43a52df1 100755 --- a/services/unbound/start.py +++ b/services/unbound/start.py @@ -5,7 +5,7 @@ import os import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") def convert(src, dst): logger = log.getLogger("convert()") diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 6bdc5e21..2d2b8735 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -160,3 +160,6 @@ REAL_IP_FROM={{ real_ip_from }} # choose wether mailu bounces (no) or rejects (yes) mail when recipient is unknown (value: yes, no) REJECT_UNLISTED_RECIPIENT={{ reject_unlisted_recipient }} + +# Log level threshold in start.py (value: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET) +LOG_LEVEL=WARNING diff --git a/webmails/rainloop/start.py b/webmails/rainloop/start.py index 3b4ac3da..defc5be6 100755 --- a/webmails/rainloop/start.py +++ b/webmails/rainloop/start.py @@ -6,7 +6,7 @@ import shutil import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") def convert(src, dst): logger = log.getLogger("convert()") diff --git a/webmails/roundcube/start.py b/webmails/roundcube/start.py index ccb5faf5..8d48d3b6 100755 --- a/webmails/roundcube/start.py +++ b/webmails/roundcube/start.py @@ -5,7 +5,7 @@ import jinja2 import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARN") +log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") def convert(src, dst): logger = log.getLogger("convert()") From 0ac3cf961783e1213691882ed97eecb1794580b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 8 Jan 2019 05:00:15 +0200 Subject: [PATCH 16/25] Don't recursivly chown on mailboxes. This fixes #776. Recursion is not needed, as the permissions will only need to be set on the first invocation. --- CHANGELOG.md | 1 + core/dovecot/Dockerfile | 3 ++- core/dovecot/start.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8277743d..05013478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ v1.6.0 - unreleased - 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)) +- Bug: Don't recursivly chown on mailboxes ([#776](https://github.com/Mailu/Mailu/issues/776)) v1.5.1 - 2017-11-21 ------------------- diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 1d4f7b91..dec2b520 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -10,7 +10,8 @@ RUN pip3 install tenacity # Image specific layers under this line RUN apk add --no-cache \ dovecot dovecot-pigeonhole-plugin dovecot-fts-lucene rspamd-client bash \ - && pip3 install podop + && pip3 install podop \ + && mkdir /var/lib/dovecot COPY conf /conf COPY start.py /start.py diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 8bf66efd..c97b8a05 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -33,5 +33,6 @@ for dovecot_file in glob.glob("/conf/*.conf"): # Run Podop, then postfix multiprocessing.Process(target=start_podop).start() -os.system("chown -R mail:mail /mail /var/lib/dovecot /conf") +os.system("chown mail:mail /mail") +os.system("chown -R mail:mail /var/lib/dovecot /conf") os.execv("/usr/sbin/dovecot", ["dovecot", "-c", "/etc/dovecot/dovecot.conf", "-F"]) From 049ca9941f256dc6c83e990e8181d383b1ef1142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 8 Jan 2019 05:16:05 +0200 Subject: [PATCH 17/25] Cleanup syntax and fix typo --- CHANGELOG.md | 2 +- core/dovecot/start.py | 2 +- core/nginx/config.py | 2 +- core/postfix/start.py | 2 +- services/rspamd/start.py | 2 +- services/unbound/start.py | 2 +- webmails/rainloop/start.py | 2 +- webmails/roundcube/start.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79aefd40..e69c37f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,8 +76,8 @@ 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: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to controll verbosity ([#588](https://github.com/Mailu/Mailu/issues/588)) - Enhancement: Include favicon package ([#801](https://github.com/Mailu/Mailu/issues/801), ([#802](https://github.com/Mailu/Mailu/issues/802)) +- Enhancement: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to control verbosity ([#588] - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) diff --git a/core/dovecot/start.py b/core/dovecot/start.py index b4289b76..d9273464 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -12,7 +12,7 @@ import sys from tenacity import retry from podop import run_server -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def start_podop(): os.setuid(8) diff --git a/core/nginx/config.py b/core/nginx/config.py index 528a3247..79370508 100755 --- a/core/nginx/config.py +++ b/core/nginx/config.py @@ -7,7 +7,7 @@ import sys args = os.environ.copy() -log.basicConfig(stream=sys.stderr, level=args["LOG_LEVEL"] if "LOG_LEVEL" in args else "WARNING") +log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING")) def convert(src, dst, args): logger = log.getLogger("convert()") diff --git a/core/postfix/start.py b/core/postfix/start.py index 5a8c2968..a06b3833 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -13,7 +13,7 @@ import sys from tenacity import retry from podop import run_server -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def start_podop(): os.setuid(100) diff --git a/services/rspamd/start.py b/services/rspamd/start.py index 047bfeba..744d4a9c 100755 --- a/services/rspamd/start.py +++ b/services/rspamd/start.py @@ -10,7 +10,7 @@ import sys from tenacity import retry -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def convert(src, dst): logger = log.getLogger("convert()") diff --git a/services/unbound/start.py b/services/unbound/start.py index 43a52df1..4dd5f3be 100755 --- a/services/unbound/start.py +++ b/services/unbound/start.py @@ -5,7 +5,7 @@ import os import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def convert(src, dst): logger = log.getLogger("convert()") diff --git a/webmails/rainloop/start.py b/webmails/rainloop/start.py index defc5be6..e2b917bf 100755 --- a/webmails/rainloop/start.py +++ b/webmails/rainloop/start.py @@ -6,7 +6,7 @@ import shutil import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def convert(src, dst): logger = log.getLogger("convert()") diff --git a/webmails/roundcube/start.py b/webmails/roundcube/start.py index 8d48d3b6..4effd965 100755 --- a/webmails/roundcube/start.py +++ b/webmails/roundcube/start.py @@ -5,7 +5,7 @@ import jinja2 import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def convert(src, dst): logger = log.getLogger("convert()") From 3e97e7e1c208bc86d8661e105b870ec0bebb3832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 8 Jan 2019 05:26:04 +0200 Subject: [PATCH 18/25] Missed a spot of cleaning syntax --- optional/clamav/start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optional/clamav/start.py b/optional/clamav/start.py index c08df509..56e1bcfe 100755 --- a/optional/clamav/start.py +++ b/optional/clamav/start.py @@ -4,7 +4,7 @@ import os import logging as log import sys -log.basicConfig(stream=sys.stderr, level=os.environ["LOG_LEVEL"] if "LOG_LEVEL" in os.environ else "WARNING") +log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) logger=log.getLogger(__name__) # Bootstrap the database if clamav is running for the first time From 77ef7317a92d56bb7c95a3d50c3667524db84d6b Mon Sep 17 00:00:00 2001 From: hoellen Date: Tue, 8 Jan 2019 14:06:19 +0200 Subject: [PATCH 19/25] Add link in changelog Co-Authored-By: muhlemmer --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69c37f4..f650ca45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,7 +77,7 @@ v1.6.0 - unreleased - 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)) -- Enhancement: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to control verbosity ([#588] +- Enhancement: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to control verbosity ([#588](https://github.com/Mailu/Mailu/issues/588)) - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) From 732b5fe161843389de061a066bce7137a36a5cf3 Mon Sep 17 00:00:00 2001 From: hoellen Date: Tue, 8 Jan 2019 19:29:34 +0100 Subject: [PATCH 20/25] change password field type in fetch creation/edit and add validators. --- CHANGELOG.md | 1 + core/admin/mailu/ui/forms.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4c83af..34c976e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ v1.6.0 - unreleased - 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)) - Bug: Don't recursivly chown on mailboxes ([#776](https://github.com/Mailu/Mailu/issues/776)) +- Bug: Fetched accounts: Password field is of type "text" ([#789](https://github.com/issues/789)) v1.5.1 - 2017-11-21 ------------------- diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index 5ee6da7d..9967fefc 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -165,11 +165,11 @@ class FetchForm(flask_wtf.FlaskForm): protocol = fields.SelectField(_('Protocol'), choices=[ ('imap', 'IMAP'), ('pop3', 'POP3') ]) - host = fields.StringField(_('Hostname or IP')) - port = fields.IntegerField(_('TCP port')) + host = fields.StringField(_('Hostname or IP'), [validators.DataRequired()]) + port = fields.IntegerField(_('TCP port'), [validators.DataRequired(), validators.NumberRange(min=0, max=65535)]) tls = fields.BooleanField(_('Enable TLS')) - username = fields.StringField(_('Username')) - password = fields.StringField(_('Password')) + username = fields.StringField(_('Username'), [validators.DataRequired()]) + password = fields.PasswordField(_('Password'), [validators.DataRequired()]) keep = fields.BooleanField(_('Keep emails on the server')) submit = fields.SubmitField(_('Submit')) From ff9558026d855302a08fe00e9903105667b49ed1 Mon Sep 17 00:00:00 2001 From: hoellen Date: Tue, 8 Jan 2019 19:49:16 +0100 Subject: [PATCH 21/25] fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c976e3..d0ce37ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,7 +111,7 @@ v1.6.0 - unreleased - 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)) - Bug: Don't recursivly chown on mailboxes ([#776](https://github.com/Mailu/Mailu/issues/776)) -- Bug: Fetched accounts: Password field is of type "text" ([#789](https://github.com/issues/789)) +- Bug: Fetched accounts: Password field is of type "text" ([#789](https://github.com/Mailu/Mailu/issues/789)) v1.5.1 - 2017-11-21 ------------------- From f08491dc469f8620caea5f70b21be5021c21e2ea Mon Sep 17 00:00:00 2001 From: hoellen Date: Wed, 9 Jan 2019 12:03:47 +0100 Subject: [PATCH 22/25] fix forced password on user edit --- CHANGELOG.md | 1 + core/admin/mailu/ui/forms.py | 2 +- core/admin/mailu/ui/views/users.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4c83af..1c6b4870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ v1.6.0 - unreleased - 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)) - Bug: Don't recursivly chown on mailboxes ([#776](https://github.com/Mailu/Mailu/issues/776)) +- Bug: Fix forced password input for user edit ([#745](https://github.com/Mailu/Mailu/issues/745)) v1.5.1 - 2017-11-21 ------------------- diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index 5ee6da7d..5312e7d9 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -84,7 +84,7 @@ class RelayForm(flask_wtf.FlaskForm): class UserForm(flask_wtf.FlaskForm): localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)]) - pw = fields.PasswordField(_('Password'), [validators.DataRequired()]) + pw = fields.PasswordField(_('Password')) pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')]) quota_bytes = fields_.IntegerSliderField(_('Quota'), default=1000000000) enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True) diff --git a/core/admin/mailu/ui/views/users.py b/core/admin/mailu/ui/views/users.py index e3c03848..8bdb76b1 100644 --- a/core/admin/mailu/ui/views/users.py +++ b/core/admin/mailu/ui/views/users.py @@ -23,6 +23,7 @@ def user_create(domain_name): return flask.redirect( flask.url_for('.user_list', domain_name=domain.name)) form = forms.UserForm() + form.pw.validators = [wtforms.validators.DataRequired()] if domain.max_quota_bytes: form.quota_bytes.validators = [ wtforms.validators.NumberRange(max=domain.max_quota_bytes)] @@ -54,7 +55,6 @@ def user_edit(user_email): # Create the form form = forms.UserForm(obj=user) wtforms_components.read_only(form.localpart) - form.pw.validators = [] form.localpart.validators = [] if max_quota_bytes: form.quota_bytes.validators = [ From a59d5dad23e1e4c6c3420441f33fcd324c3926df Mon Sep 17 00:00:00 2001 From: hoellen Date: Wed, 9 Jan 2019 12:52:05 +0100 Subject: [PATCH 23/25] fix edit of fetched acc without changing password --- core/admin/mailu/ui/forms.py | 2 +- core/admin/mailu/ui/views/fetches.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/admin/mailu/ui/forms.py b/core/admin/mailu/ui/forms.py index 9967fefc..b6b6f34e 100644 --- a/core/admin/mailu/ui/forms.py +++ b/core/admin/mailu/ui/forms.py @@ -169,7 +169,7 @@ class FetchForm(flask_wtf.FlaskForm): port = fields.IntegerField(_('TCP port'), [validators.DataRequired(), validators.NumberRange(min=0, max=65535)]) tls = fields.BooleanField(_('Enable TLS')) username = fields.StringField(_('Username'), [validators.DataRequired()]) - password = fields.PasswordField(_('Password'), [validators.DataRequired()]) + password = fields.PasswordField(_('Password')) keep = fields.BooleanField(_('Keep emails on the server')) submit = fields.SubmitField(_('Submit')) diff --git a/core/admin/mailu/ui/views/fetches.py b/core/admin/mailu/ui/views/fetches.py index d9f55404..f2049fe9 100644 --- a/core/admin/mailu/ui/views/fetches.py +++ b/core/admin/mailu/ui/views/fetches.py @@ -3,6 +3,7 @@ from mailu.ui import ui, forms, access import flask import flask_login +import wtforms @ui.route('/fetch/list', methods=['GET', 'POST'], defaults={'user_email': None}) @@ -21,6 +22,7 @@ def fetch_create(user_email): user_email = user_email or flask_login.current_user.email user = models.User.query.get(user_email) or flask.abort(404) form = forms.FetchForm() + form.pw.validators = [wtforms.validators.DataRequired()] if form.validate_on_submit(): fetch = models.Fetch(user=user) form.populate_obj(fetch) @@ -38,6 +40,8 @@ def fetch_edit(fetch_id): fetch = models.Fetch.query.get(fetch_id) or flask.abort(404) form = forms.FetchForm(obj=fetch) if form.validate_on_submit(): + if not form.password.data: + form.password.data = fetch.password form.populate_obj(fetch) models.db.session.commit() flask.flash('Fetch configuration updated') From b65d70cf1eb9bdb98578f0b0399fd387af94c0d1 Mon Sep 17 00:00:00 2001 From: hoellen Date: Wed, 9 Jan 2019 19:53:52 +0100 Subject: [PATCH 24/25] mark spam as seen --- CHANGELOG.md | 1 + core/dovecot/conf/report-spam.sieve | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ce37ae..735dd75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ v1.6.0 - unreleased - Enhancement: Update documentation - Enhancement: Include favicon package ([#801](https://github.com/Mailu/Mailu/issues/801), ([#802](https://github.com/Mailu/Mailu/issues/802)) - Enhancement: Add logging at critical places in python start.py scripts. Implement LOG_LEVEL to control verbosity ([#588](https://github.com/Mailu/Mailu/issues/588)) +- Enhancement: Mark message as seen when reporting as spam - Upstream: Update Roundcube - Upstream: Update Rainloop - Bug: Rainloop fails with "domain not allowed" ([#93](https://github.com/Mailu/Mailu/issues/93)) diff --git a/core/dovecot/conf/report-spam.sieve b/core/dovecot/conf/report-spam.sieve index 108d6210..87fd515e 100644 --- a/core/dovecot/conf/report-spam.sieve +++ b/core/dovecot/conf/report-spam.sieve @@ -1,3 +1,5 @@ +require "imap4flags"; require "vnd.dovecot.execute"; +setflag "\\seen"; execute :pipe "spam"; From 492f3867d891577d941ca0325ef9d9a4e79bcb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Thu, 10 Jan 2019 12:07:42 +0100 Subject: [PATCH 25/25] remove (broken) FTS --- CHANGELOG.md | 1 - core/dovecot/Dockerfile | 2 +- core/dovecot/conf/dovecot.conf | 16 ---------------- docs/compose/.env | 4 ---- 4 files changed, 1 insertion(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3632f9ff..0458ab5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,6 @@ v1.6.0 - unreleased - Feature: Add posibilty to run webmail on root ([#501](https://github.com/Mailu/Mailu/issues/501)) - Feature: Upgrade docker-compose.yml to version 3 ([#539](https://github.com/Mailu/Mailu/issues/539)) - Feature: Documentation to deploy mailu on a docker swarm ([#551](https://github.com/Mailu/Mailu/issues/551)) -- Feature: Add full-text search support ([#552](https://github.com/Mailu/Mailu/issues/552)) - Feature: Add optional Maildir-Compression ([#553](https://github.com/Mailu/Mailu/issues/553)) - Feature: Preserve rspamd history on container restart ([#561](https://github.com/Mailu/Mailu/issues/561)) - Feature: FAQ ([#564](https://github.com/Mailu/Mailu/issues/564), [#677](https://github.com/Mailu/Mailu/issues/677)) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index dec2b520..83d23b52 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -9,7 +9,7 @@ RUN pip3 install jinja2 RUN pip3 install tenacity # Image specific layers under this line RUN apk add --no-cache \ - dovecot dovecot-pigeonhole-plugin dovecot-fts-lucene rspamd-client bash \ + dovecot dovecot-pigeonhole-plugin rspamd-client bash \ && pip3 install podop \ && mkdir /var/lib/dovecot diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 83c78f16..b7cca76c 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -7,22 +7,6 @@ postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }} hostname = {{ HOSTNAMES.split(",")[0] }} submission_host = {{ FRONT_ADDRESS }} -{% if DISABLE_FTS_LUCENE != 'true' %} -############### -# Full-text search -############### -mail_plugins = $mail_plugins fts fts_lucene - -plugin { - fts = lucene - - fts_autoindex = yes - fts_autoindex_exclude = \Junk - - fts_lucene = whitespace_chars=@. -} -{% endif %} - ############### # Mailboxes ############### diff --git a/docs/compose/.env b/docs/compose/.env index f65c2f01..cf906b58 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -3,10 +3,6 @@ # these few settings must however be configured before starting the mail # server and require a restart upon change. -# Set this to `true` to disable full text search by lucene (value: true, false) -# This is a workaround for the bug in issue #751 (indexer-worker crashes) -DISABLE_FTS_LUCENE=false - ################################### # Common configuration variables ###################################