diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2b81a60d..e2a535dd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ on: - '[1-9].[0-9].[0-9]' # pre-releases, e.g. 1.8-pre1 - 1.8-pre[0-9] - # test branches, e.g. test-debian + # test branches, e.g. test-debian - test-* ############################################### @@ -39,6 +39,21 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + #For branch TESTING, we set the image tag to PR-xxxx + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -58,7 +73,7 @@ jobs: run: echo "$DOCKER_PW" | docker login --username $DOCKER_UN --password-stdin - name: Build all docker images env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} run: docker-compose -f tests/build.yml build @@ -76,6 +91,20 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -92,9 +121,9 @@ jobs: - name: Copy all certs run: sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' - name: Test core suite - run: python tests/compose/test.py core 1 + run: python tests/compose/test.py core 2 env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} @@ -109,6 +138,20 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -125,9 +168,9 @@ jobs: - name: Copy all certs run: sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' - name: Test fetch - run: python tests/compose/test.py fetchmail 1 + run: python tests/compose/test.py fetchmail 2 env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} @@ -142,6 +185,20 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -158,9 +215,9 @@ jobs: - name: Copy all certs run: sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' - name: Test clamvav - run: python tests/compose/test.py filters 2 + run: python tests/compose/test.py filters 3 env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} @@ -175,6 +232,20 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -191,9 +262,9 @@ jobs: - name: Copy all certs run: sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' - name: Test rainloop - run: python tests/compose/test.py rainloop 1 + run: python tests/compose/test.py rainloop 2 env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} @@ -208,6 +279,20 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -224,9 +309,9 @@ jobs: - name: Copy all certs run: sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' - name: Test roundcube - run: python tests/compose/test.py roundcube 1 + run: python tests/compose/test.py roundcube 2 env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} @@ -241,6 +326,20 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -257,9 +356,9 @@ jobs: - name: Copy all certs run: sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*' - name: Test webdav - run: python tests/compose/test.py webdav 1 + run: python tests/compose/test.py webdav 2 env: - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} @@ -280,6 +379,21 @@ jobs: shell: bash run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + #For branch TESTING, we set the image tag to PR-xxxx + - name: Derive MAILU_VERSION for branch testing + if: ${{ env.BRANCH == 'testing' }} + shell: bash + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + echo "MAILU_VERSION=pr-${COMMIT_MESSAGE//[!0-9]/}" >> $GITHUB_ENV + - name: Derive MAILU_VERSION for other branches than testing + if: ${{ env.BRANCH != 'testing' }} + shell: bash + env: + MAILU_BRANCH: ${{ env.BRANCH }} + run: | + echo "MAILU_VERSION=${{ env.MAILU_BRANCH }}" >> $GITHUB_ENV - name: Create folder for storing images run: | sudo mkdir -p /images @@ -300,9 +414,8 @@ jobs: DOCKER_PW: ${{ secrets.Docker_Password }} DOCKER_ORG: ${{ secrets.DOCKER_ORG }} DOCKER_ORG_TESTS: ${{ secrets.DOCKER_ORG_TESTS }} - MAILU_VERSION: ${{ env.BRANCH }} + MAILU_VERSION: ${{ env.MAILU_VERSION }} TRAVIS_BRANCH: ${{ env.BRANCH }} - TRAVIS_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} run: bash tests/deploy.sh # This job is watched by bors. It only complets if building,testing and deploy worked. diff --git a/CHANGELOG.md b/CHANGELOG.md index 579f3e82..da945c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,49 @@ Changelog Upgrade should run fine as long as you generate a new compose or stack configuration and upgrade your mailu.env. -Please note that the current 1.8 is what we call a "soft release": It’s there for everyone to see and use, but to limit possible user-impact of this very big release, it’s not yet the default in the setup-utility for new users. When upgrading, please treat it with some care, and be sure to always have backups! - There are some changes to the configuration overrides. Override files are now mounted read-only into the containers. The Dovecot and Postfix overrides are moved in their own sub-directory. If there are local override files, they will need to be moved from overrides/ to overrides/dovecot and overrides/postfix/. See https://mailu.io/1.8/faq.html#how-can-i-override-settings for all the mappings. -Please note that the shipped image for PostgreSQL database is deprecated. -We advise to switch to an external database server. +One major change for the docker compose file is that the antispam container needs a fixed hostname [#1837](https://github.com/Mailu/Mailu/issues/1837). +This is handled when you regenerate the docker-compose file. A fixed hostname is required to retain rspamd history. +This is also handled in the helm-chart repo. - -v1.8.0 - 2020-09-28 +Improvements have been made to protect again session-fixation attacks. +To be fully protected, it is required to change your SECRET_KEY in Mailu.env after upgrading. +A new SECRET_KEY is generated when you recreate your docker-compose.yml & mailu.env file via setup.mailu.io. + +The SECRET_KEY is an uppercase alphanumeric string of length 16. You can manually create such a string via +```cat /dev/urandom | tr -dc 'A-Z0-9' | fold -w ${1:-16} | head -n 1``` + +After changing mailu.env, it is required to recreate all containers for the changes to be propagated. + +Please note that the shipped image for PostgreSQL database is deprecated. +We advise to switch to an external PostgreSQL database server. + + +1.8.0 - 2021-08-06 +-------------------- + +- Features: Update version of roundcube webmail and carddav plugin. This is a security update. ([#1841](https://github.com/Mailu/Mailu/issues/1841)) +- Features: Update version of rainloop webmail to 1.16.0. This is a security update. ([#1845](https://github.com/Mailu/Mailu/issues/1845)) +- Features: Changed default value of AUTH_RATELIMIT_SUBNET to false. Increased default value of the rate limit in setup utility (AUTH_RATELIMIT) to a higher value. ([#1867](https://github.com/Mailu/Mailu/issues/1867)) +- Features: Update jquery used in setup. Set pinned versions in requirements.txt for setup. This is a security update. ([#1880](https://github.com/Mailu/Mailu/issues/1880)) +- Bugfixes: Replace PUBLIC_HOSTNAME and PUBLIC_IP in "Received" headers to ensure that no undue spam points are attributed ([#191](https://github.com/Mailu/Mailu/issues/191)) +- Bugfixes: Don't replace nested headers (typically in attached emails) ([#1660](https://github.com/Mailu/Mailu/issues/1660)) +- Bugfixes: Fix letsencrypt access to certbot for the mail-letsencrypt flavour ([#1686](https://github.com/Mailu/Mailu/issues/1686)) +- Bugfixes: Fix CVE-2020-25275 and CVE-2020-24386 by upgrading alpine for + dovecot which contains a fixed dovecot version. ([#1720](https://github.com/Mailu/Mailu/issues/1720)) +- Bugfixes: Antispam service now uses a static hostname. Rspamd history is only retained when the service has a fixed hostname. ([#1837](https://github.com/Mailu/Mailu/issues/1837)) +- Bugfixes: Fix a bug preventing colons from being used in passwords when using radicale/webdav. ([#1861](https://github.com/Mailu/Mailu/issues/1861)) +- Bugfixes: Remove dot in blueprint name to prevent critical flask startup error in setup. ([#1874](https://github.com/Mailu/Mailu/issues/1874)) +- Bugfixes: fix punycode encoding of domain names ([#1891](https://github.com/Mailu/Mailu/issues/1891)) +- Improved Documentation: Update fail2ban documentation to use systemd backend instead of filepath for journald ([#1857](https://github.com/Mailu/Mailu/issues/1857)) +- Misc: Switch from client side (cookie) sessions to server side sessions and protect against session-fixation attacks. We recommend that you change your SECRET_KEY after upgrading. ([#1783](https://github.com/Mailu/Mailu/issues/1783)) + + +v1.8.0rc - 2020-09-28 -------------------- - Features: Add support for backward-forwarding using SRS ([#328](https://github.com/Mailu/Mailu/issues/328)) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 059318fc..8fb3265d 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -13,4 +13,4 @@ Before we can consider review and merge, please make sure the following list is 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: add [changelog](https://mailu.io/master/contributors/guide.html#changelog) entry file. +- [ ] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. diff --git a/core/admin/Dockerfile b/core/admin/Dockerfile index f3b8643c..fa75e8dc 100644 --- a/core/admin/Dockerfile +++ b/core/admin/Dockerfile @@ -1,8 +1,9 @@ # First stage to build assets -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 ARG ARCH="" -FROM ${ARCH}node:8 as assets -COPY --from=balenalib/rpi-alpine:3.10 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static + +FROM ${ARCH}node:16 as assets +COPY --from=balenalib/rpi-alpine:3.14 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static COPY package.json ./ RUN npm install @@ -24,9 +25,9 @@ RUN mkdir -p /app WORKDIR /app COPY requirements-prod.txt requirements.txt -RUN apk add --no-cache libressl curl postgresql-libs mariadb-connector-c \ +RUN apk add --no-cache openssl curl postgresql-libs mariadb-connector-c \ && apk add --no-cache --virtual build-dep \ - libressl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev \ + openssl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \ && pip3 install -r requirements.txt \ && apk del --no-cache build-dep diff --git a/core/admin/assets/app.css b/core/admin/assets/app.css index e1656429..8351eed8 100644 --- a/core/admin/assets/app.css +++ b/core/admin/assets/app.css @@ -20,3 +20,4 @@ .sidebar-toggle { padding: unset !important; } + diff --git a/core/admin/assets/app.js b/core/admin/assets/app.js index dc981081..364f8429 100644 --- a/core/admin/assets/app.js +++ b/core/admin/assets/app.js @@ -1,10 +1,17 @@ require('./app.css'); -import 'select2'; +import 'admin-lte/plugins/select2/js/select2.js'; +import 'admin-lte/plugins/datatables/jquery.dataTables.js'; +import 'admin-lte/plugins/datatables-bs4/js/dataTables.bootstrap4.js'; +import 'admin-lte/plugins/datatables-responsive/js/dataTables.responsive.js'; +import 'admin-lte/plugins/datatables-responsive/js/responsive.bootstrap4.js'; + jQuery("document").ready(function() { jQuery(".mailselect").select2({ tags: true, tokenSeparators: [',', ' '] - }) + }); + jQuery(".dataTable").DataTable({ + "responsive": true, + }); }); - diff --git a/core/admin/assets/vendor.js b/core/admin/assets/vendor.js index f7ed03c8..fd43d918 100644 --- a/core/admin/assets/vendor.js +++ b/core/admin/assets/vendor.js @@ -1,19 +1,22 @@ // jQuery import jQuery from 'jquery'; -import 'select2/dist/css/select2.css'; +import 'admin-lte/plugins/select2/css/select2.css'; // bootstrap -import 'bootstrap/less/bootstrap.less'; -import 'bootstrap'; +// import 'bootstrap/less/bootstrap.less'; +// import 'bootstrap'; -// FA -import 'font-awesome/scss/font-awesome.scss'; +// FontAwesome +import 'admin-lte/plugins/fontawesome-free/css/fontawesome.css'; +import 'admin-lte/plugins/fontawesome-free/css/regular.css'; +import 'admin-lte/plugins/fontawesome-free/css/solid.css'; // AdminLTE -import 'admin-lte/build/less/AdminLTE-without-plugins.less'; -import 'admin-lte/build/less/select2.less'; -import 'admin-lte/build/less/skins/skin-blue.less'; +import 'admin-lte/build/scss/adminlte.scss'; +import 'admin-lte/plugins/datatables-bs4/css/dataTables.bootstrap4.css'; +import 'admin-lte/plugins/datatables-responsive/css/responsive.bootstrap4.css'; +import 'admin-lte/plugins/bootstrap/js/bootstrap.js'; +import 'admin-lte/build/js/AdminLTE.js'; import 'admin-lte/build/js/Layout.js'; import 'admin-lte/build/js/ControlSidebar.js'; import 'admin-lte/build/js/PushMenu.js'; -import 'admin-lte/build/js/BoxRefresh.js'; diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index 3d1b4fb5..7cd3a56b 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -32,10 +32,11 @@ DEFAULT_CONFIG = { 'DOMAIN': 'mailu.io', 'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io', 'POSTMASTER': 'postmaster', + 'WILDCARD_SENDERS': '', 'TLS_FLAVOR': 'cert', 'INBOUND_TLS_ENFORCE': False, - 'AUTH_RATELIMIT': '10/minute;1000/hour', - 'AUTH_RATELIMIT_SUBNET': True, + 'AUTH_RATELIMIT': '1000/minute;10000/hour', + 'AUTH_RATELIMIT_SUBNET': False, 'DISABLE_STATISTICS': False, # Mail settings 'DMARC_RUA': None, @@ -46,6 +47,7 @@ DEFAULT_CONFIG = { 'DKIM_SELECTOR': 'dkim', 'DKIM_PATH': '/dkim/{domain}.{selector}.key', 'DEFAULT_QUOTA': 1000000000, + 'MESSAGE_RATELIMIT': '200/day', # Web settings 'SITENAME': 'Mailu', 'WEBSITE': 'https://mailu.io', diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 3f5582cc..5e60cd0c 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -81,6 +81,13 @@ def handle_authentication(headers): raw_password = urllib.parse.unquote(headers["Auth-Pass"]) password = raw_password.encode("iso8859-1").decode("utf8") ip = urllib.parse.unquote(headers["Client-Ip"]) + service_port = int(urllib.parse.unquote(headers["Auth-Port"])) + if service_port == 25: + return { + "Auth-Status": "AUTH not supported", + "Auth-Error-Code": "502 5.5.1", + "Auth-Wait": 0 + } user = models.User.query.get(user_email) if check_credentials(user, password, ip, protocol): return { diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py index 8ff10aed..1686e1cb 100644 --- a/core/admin/mailu/internal/views/auth.py +++ b/core/admin/mailu/internal/views/auth.py @@ -50,7 +50,7 @@ def user_authentication(): if (not flask_login.current_user.is_anonymous and flask_login.current_user.enabled): response = flask.Response() - response.headers["X-User"] = flask_login.current_user.get_id() + response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, flask_login.current_user.get_id(), "") response.headers["X-User-Token"] = models.User.get_temp_token(flask_login.current_user.get_id()) return response return flask.abort(403) @@ -63,11 +63,11 @@ def basic_authentication(): authorization = flask.request.headers.get("Authorization") if authorization and authorization.startswith("Basic "): encoded = authorization.replace("Basic ", "") - user_email, password = base64.b64decode(encoded).split(b":") + user_email, password = base64.b64decode(encoded).split(b":", 1) user = models.User.query.get(user_email.decode("utf8")) if nginx.check_credentials(user, password.decode('utf-8'), flask.request.remote_addr, "web"): response = flask.Response() - response.headers["X-User"] = user.email + response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, user.email, "") return response response = flask.Response(status=401) response.headers["WWW-Authenticate"] = 'Basic realm="Login Required"' diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py index c358c37f..2e7d0b9b 100644 --- a/core/admin/mailu/internal/views/postfix.py +++ b/core/admin/mailu/internal/views/postfix.py @@ -1,5 +1,6 @@ -from mailu import models +from mailu import models, utils from mailu.internal import internal +from flask import current_app as app import flask import idna @@ -31,7 +32,6 @@ def postfix_alias_map(alias): destination = models.Email.resolve_destination(localpart, domain_name) return flask.jsonify(",".join(destination)) if destination else flask.abort(404) - @internal.route("/postfix/transport/") def postfix_transport(email): if email == '*' or re.match("(^|.*@)\[.*\]$", email): @@ -133,12 +133,20 @@ def postfix_sender_map(sender): @internal.route("/postfix/sender/login/") def postfix_sender_login(sender): + wildcard_senders = [s for s in flask.current_app.config.get('WILDCARD_SENDERS', '').lower().replace(' ', '').split(',') if s] localpart, domain_name = models.Email.resolve_domain(sender) if localpart is None: - return flask.abort(404) + return flask.jsonify(",".join(wildcard_senders)) if wildcard_senders else flask.abort(404) destination = models.Email.resolve_destination(localpart, domain_name, True) + destination = [*destination, *wildcard_senders] if destination else [*wildcard_senders] return flask.jsonify(",".join(destination)) if destination else flask.abort(404) +@internal.route("/postfix/sender/rate/") +def postfix_sender_rate(sender): + """ Rate limit outbound emails per sender login + """ + user = models.User.get(sender) or flask.abort(404) + return flask.abort(404) if user.sender_limiter.hit() else flask.jsonify("450 4.2.1 You are sending too many emails too fast.") @internal.route("/postfix/sender/access/") def postfix_sender_access(sender): diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 43f7196a..cdc5a579 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -27,7 +27,7 @@ from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.inspection import inspect from werkzeug.utils import cached_property -from mailu import dkim +from mailu import dkim, utils db = flask_sqlalchemy.SQLAlchemy() @@ -57,10 +57,9 @@ class IdnaEmail(db.TypeDecorator): def process_bind_param(self, value, dialect): """ encode unicode domain part of email address to punycode """ - localpart, domain_name = value.rsplit('@', 1) + localpart, domain_name = value.lower().rsplit('@', 1) if '@' in localpart: raise ValueError('email local part must not contain "@"') - domain_name = domain_name.lower() return f'{localpart}@{idna.encode(domain_name).decode("ascii")}' def process_result_value(self, value, dialect): @@ -272,11 +271,12 @@ class Domain(Base): return dkim.strip_key(dkim_key).decode('utf8') def generate_dkim_key(self): - """ generate and activate new DKIM key """ + """ generate new DKIM key """ self.dkim_key = dkim.gen_key() def has_email(self, localpart): """ checks if localpart is configured for domain """ + localpart = localpart.lower() for email in chain(self.users, self.aliases): if email.localpart == localpart: return True @@ -355,8 +355,8 @@ class Email(object): @email.setter def email(self, value): """ setter for email - sets _email, localpart and domain_name at once """ - self.localpart, self.domain_name = value.rsplit('@', 1) - self._email = value + self._email = value.lower() + self.localpart, self.domain_name = self._email.rsplit('@', 1) @staticmethod def _update_localpart(target, value, *_): @@ -371,8 +371,8 @@ class Email(object): @classmethod def __declare_last__(cls): # gets called after mappings are completed - sqlalchemy.event.listen(User.localpart, 'set', cls._update_localpart, propagate=True) - sqlalchemy.event.listen(User.domain_name, 'set', cls._update_domain_name, propagate=True) + sqlalchemy.event.listen(cls.localpart, 'set', cls._update_localpart, propagate=True) + sqlalchemy.event.listen(cls.domain_name, 'set', cls._update_domain_name, propagate=True) def sendmail(self, subject, body): """ send an email to the address """ @@ -389,8 +389,7 @@ class Email(object): def resolve_domain(cls, email): """ resolves domain alternative to real domain """ localpart, domain_name = email.rsplit('@', 1) if '@' in email else (None, email) - alternative = Alternative.query.get(domain_name) - if alternative: + if alternative := Alternative.query.get(domain_name): domain_name = alternative.domain_name return (localpart, domain_name) @@ -401,12 +400,14 @@ class Email(object): localpart_stripped = None stripped_alias = None - if os.environ.get('RECIPIENT_DELIMITER') in localpart: - localpart_stripped = localpart.rsplit(os.environ.get('RECIPIENT_DELIMITER'), 1)[0] + delim = os.environ.get('RECIPIENT_DELIMITER') + if delim in localpart: + localpart_stripped = localpart.rsplit(delim, 1)[0] user = User.query.get(f'{localpart}@{domain_name}') if not user and localpart_stripped: user = User.query.get(f'{localpart_stripped}@{domain_name}') + if user: email = f'{localpart}@{domain_name}' @@ -416,15 +417,15 @@ class Email(object): destination.append(email) else: destination = [email] + return destination pure_alias = Alias.resolve(localpart, domain_name) - stripped_alias = Alias.resolve(localpart_stripped, domain_name) if pure_alias and not pure_alias.wildcard: return pure_alias.destination - if stripped_alias: + if stripped_alias := Alias.resolve(localpart_stripped, domain_name): return stripped_alias.destination if pure_alias: @@ -500,6 +501,12 @@ class User(Base, Email): self.reply_enddate > now ) + @property + def sender_limiter(self): + return utils.limiter.get_limiter( + app.config["MESSAGE_RATELIMIT"], "sender", self.email + ) + @classmethod def get_password_context(cls): """ create password context for hashing and verification diff --git a/core/admin/mailu/translations/he/LC_MESSAGES/messages.po b/core/admin/mailu/translations/he/LC_MESSAGES/messages.po index e884b737..91fbbcff 100644 --- a/core/admin/mailu/translations/he/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/he/LC_MESSAGES/messages.po @@ -1,29 +1,30 @@ -# Translations template for PROJECT. -# Copyright (C) 2018 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2018. +# Translations template for Mailu. +# Copyright (C) 2018 Mailu +# This file is distributed under the same license as the Mailu project. +# Modi Sacks, 2019-2021. +# Yaron Shahrabani , 2021. # msgid "" msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"Project-Id-Version: Mailu 1.5.1\n" +"Report-Msgid-Bugs-To: heb-bugzap@projects.hamakor.org.il \n" "POT-Creation-Date: 2018-04-22 12:10+0200\n" -"PO-Revision-Date: 2019-11-27 22:20+0000\n" -"Last-Translator: Mordi Sacks \n" +"PO-Revision-Date: 2021-07-19 09:04+0300\n" +"Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" "Language: he\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 3.3\n" +"X-Generator: Poedit 3.0\n" "Generated-By: Babel 2.5.3\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." -msgstr "כתובת דוא\"ל לא חוקית." +msgstr "כתובת דוא״ל שגויה." #: mailu/ui/forms.py:36 msgid "Confirm" @@ -31,7 +32,7 @@ msgstr "אישור" #: mailu/ui/forms.py:40 mailu/ui/forms.py:77 msgid "E-mail" -msgstr "דוא\"ל" +msgstr "דוא״ל" #: mailu/ui/forms.py:41 mailu/ui/forms.py:78 mailu/ui/forms.py:90 #: mailu/ui/forms.py:109 mailu/ui/forms.py:162 @@ -48,23 +49,23 @@ msgstr "כניסה" #: mailu/ui/templates/domain/details.html:27 #: mailu/ui/templates/domain/list.html:18 mailu/ui/templates/relay/list.html:17 msgid "Domain name" -msgstr "שם דומיין" +msgstr "שם תחום" #: mailu/ui/forms.py:47 msgid "Maximum user count" -msgstr "" +msgstr "כמות המשתמשים המרבית" #: mailu/ui/forms.py:48 msgid "Maximum alias count" -msgstr "" +msgstr "כמות הכינויים המרבית" #: mailu/ui/forms.py:49 msgid "Maximum user quota" -msgstr "" +msgstr "מיכסת המשתמשים המרבית" #: mailu/ui/forms.py:50 msgid "Enable sign-up" -msgstr "" +msgstr "לאפשר הרשמה" #: mailu/ui/forms.py:51 mailu/ui/forms.py:72 mailu/ui/forms.py:83 #: mailu/ui/forms.py:128 mailu/ui/forms.py:140 @@ -72,53 +73,53 @@ msgstr "" #: mailu/ui/templates/relay/list.html:19 mailu/ui/templates/token/list.html:19 #: mailu/ui/templates/user/list.html:23 msgid "Comment" -msgstr "" +msgstr "תגובה" #: mailu/ui/forms.py:52 mailu/ui/forms.py:61 mailu/ui/forms.py:66 #: mailu/ui/forms.py:73 mailu/ui/forms.py:132 mailu/ui/forms.py:141 msgid "Create" -msgstr "" +msgstr "יצירה" #: mailu/ui/forms.py:57 msgid "Initial admin" -msgstr "" +msgstr "מנהל ראשוני" #: mailu/ui/forms.py:58 msgid "Admin password" -msgstr "" +msgstr "סיסמת ניהול" #: mailu/ui/forms.py:59 mailu/ui/forms.py:79 mailu/ui/forms.py:91 msgid "Confirm password" -msgstr "" +msgstr "אישור סיסמה" #: mailu/ui/forms.py:65 msgid "Alternative name" -msgstr "" +msgstr "שם חלופי" #: mailu/ui/forms.py:70 msgid "Relayed domain name" -msgstr "" +msgstr "שם תחום מועבר" #: mailu/ui/forms.py:71 mailu/ui/templates/relay/list.html:18 msgid "Remote host" -msgstr "" +msgstr "מארח מרוחק" #: mailu/ui/forms.py:80 mailu/ui/templates/user/list.html:22 #: mailu/ui/templates/user/signup_domain.html:16 msgid "Quota" -msgstr "" +msgstr "מיכסה" #: mailu/ui/forms.py:81 msgid "Allow IMAP access" -msgstr "" +msgstr "לאפשר גישה ב־IMAP" #: mailu/ui/forms.py:82 msgid "Allow POP3 access" -msgstr "" +msgstr "לאפשר גישה ב־POP3" #: mailu/ui/forms.py:84 msgid "Enabled" -msgstr "" +msgstr "מופעל" #: mailu/ui/forms.py:85 msgid "Save" @@ -126,7 +127,7 @@ msgstr "שמירה" #: mailu/ui/forms.py:89 msgid "Email address" -msgstr "דואר אלקטרוני" +msgstr "כתובת דוא״ל" #: mailu/ui/forms.py:93 mailu/ui/templates/sidebar.html:117 #: mailu/ui/templates/user/signup.html:4 @@ -136,244 +137,244 @@ msgstr "הרשמה" #: mailu/ui/forms.py:97 msgid "Displayed name" -msgstr "" +msgstr "שם מוצג" #: mailu/ui/forms.py:98 msgid "Enable spam filter" -msgstr "" +msgstr "הפעלת מסנן ספאם" #: mailu/ui/forms.py:99 msgid "Spam filter tolerance" -msgstr "" +msgstr "סובלנות מסנן הספאם" #: mailu/ui/forms.py:100 msgid "Enable forwarding" -msgstr "" +msgstr "הפעלת העברה" #: mailu/ui/forms.py:101 msgid "Keep a copy of the emails" -msgstr "" +msgstr "להשאיר עותק מההודעות" #: mailu/ui/forms.py:103 mailu/ui/forms.py:139 #: mailu/ui/templates/alias/list.html:20 msgid "Destination" -msgstr "" +msgstr "יעד" #: mailu/ui/forms.py:105 msgid "Save settings" -msgstr "" +msgstr "שמירת הגדרות" #: mailu/ui/forms.py:110 msgid "Password check" -msgstr "" +msgstr "בדיקת סיסמה" #: mailu/ui/forms.py:111 mailu/ui/templates/sidebar.html:16 msgid "Update password" -msgstr "" +msgstr "עדכון סיסמה" #: mailu/ui/forms.py:115 msgid "Enable automatic reply" -msgstr "" +msgstr "הפעלת תגובה אוטומטית" #: mailu/ui/forms.py:116 msgid "Reply subject" -msgstr "" +msgstr "נושא התגובה" #: mailu/ui/forms.py:117 msgid "Reply body" -msgstr "" +msgstr "גוף התגובה" #: mailu/ui/forms.py:119 msgid "End of vacation" -msgstr "" +msgstr "סוף החופשה" #: mailu/ui/forms.py:120 msgid "Update" -msgstr "" +msgstr "עדכון" #: mailu/ui/forms.py:125 msgid "Your token (write it down, as it will never be displayed again)" -msgstr "" +msgstr "האסימון שלך (כדאי לשמור עליו היטב כיוון שהוא לא יופיע פעם נוספת)" #: mailu/ui/forms.py:130 mailu/ui/templates/token/list.html:20 msgid "Authorized IP" -msgstr "" +msgstr "כתובת IP מורשית" #: mailu/ui/forms.py:136 msgid "Alias" -msgstr "" +msgstr "כינוי" #: mailu/ui/forms.py:138 msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" -msgstr "" +msgstr "להשתמש בתחביר דמוי SQL (למשל: catch-all aliases)" #: mailu/ui/forms.py:145 msgid "Admin email" -msgstr "" +msgstr "דוא״ל ההנהלה" #: mailu/ui/forms.py:146 mailu/ui/forms.py:151 mailu/ui/forms.py:164 msgid "Submit" -msgstr "" +msgstr "הגשה" #: mailu/ui/forms.py:150 msgid "Manager email" -msgstr "" +msgstr "דוא״ל המפקח" #: mailu/ui/forms.py:155 msgid "Protocol" -msgstr "" +msgstr "פרוטוקול" #: mailu/ui/forms.py:158 msgid "Hostname or IP" -msgstr "" +msgstr "שם מארח או כתובת IP" #: mailu/ui/forms.py:159 mailu/ui/templates/client.html:20 #: mailu/ui/templates/client.html:47 msgid "TCP port" -msgstr "" +msgstr "פתחת TCP" #: mailu/ui/forms.py:160 msgid "Enable TLS" -msgstr "" +msgstr "הפעלת TLS" #: mailu/ui/forms.py:161 mailu/ui/templates/client.html:28 #: mailu/ui/templates/client.html:55 mailu/ui/templates/fetch/list.html:20 msgid "Username" -msgstr "" +msgstr "שם משתמש" #: mailu/ui/forms.py:163 msgid "Keep emails on the server" -msgstr "" +msgstr "להשאיר את ההודעות על השרת" #: mailu/ui/forms.py:168 msgid "Announcement subject" -msgstr "" +msgstr "נושא ההכרזה" #: mailu/ui/forms.py:170 msgid "Announcement body" -msgstr "" +msgstr "גוף ההכרזה" #: mailu/ui/forms.py:172 msgid "Send" -msgstr "" +msgstr "שליחה" #: mailu/ui/templates/announcement.html:4 msgid "Public announcement" -msgstr "" +msgstr "הכרזה פומבית" #: mailu/ui/templates/client.html:4 mailu/ui/templates/sidebar.html:82 msgid "Client setup" -msgstr "" +msgstr "הגדרת לקוח" #: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:43 msgid "Mail protocol" -msgstr "" +msgstr "פרוטוקול דוא״ל" #: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:51 msgid "Server name" -msgstr "" +msgstr "שם שרת" #: mailu/ui/templates/confirm.html:4 msgid "Confirm action" -msgstr "" +msgstr "אישור הפעולה" #: mailu/ui/templates/confirm.html:13 #, python-format msgid "You are about to %(action)s. Please confirm your action." -msgstr "" +msgstr "פעולה זו תבצע %(action)s. נא לאשר את הפעולה שלך." #: mailu/ui/templates/docker-error.html:4 msgid "Docker error" -msgstr "" +msgstr "שגיאת Docker" #: mailu/ui/templates/docker-error.html:12 msgid "An error occurred while talking to the Docker server." -msgstr "" +msgstr "אירעה שגיאה בעת החיבור לשרת ה־Docker." #: mailu/ui/templates/login.html:8 msgid "to access the administration tools" -msgstr "" +msgstr "כדי לגשת לכלי הניהול" #: mailu/ui/templates/sidebar.html:11 mailu/ui/templates/user/list.html:34 msgid "Settings" -msgstr "" +msgstr "הגדרות" #: mailu/ui/templates/sidebar.html:21 mailu/ui/templates/user/list.html:35 msgid "Auto-reply" -msgstr "" +msgstr "מענה אוטומטית" #: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:26 #: mailu/ui/templates/user/list.html:36 msgid "Fetched accounts" -msgstr "" +msgstr "חשבונות נמשכים" #: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/token/list.html:4 msgid "Authentication tokens" -msgstr "" +msgstr "אסימוני אימות" #: mailu/ui/templates/sidebar.html:35 msgid "Administration" -msgstr "" +msgstr "ניהול" #: mailu/ui/templates/sidebar.html:44 msgid "Announcement" -msgstr "" +msgstr "הכרזה" #: mailu/ui/templates/sidebar.html:49 msgid "Administrators" -msgstr "" +msgstr "מנהלים" #: mailu/ui/templates/sidebar.html:54 msgid "Relayed domains" -msgstr "" +msgstr "שמות תחום מועברים" #: mailu/ui/templates/sidebar.html:59 mailu/ui/templates/user/settings.html:15 msgid "Antispam" -msgstr "" +msgstr "מניעת ספאם" #: mailu/ui/templates/sidebar.html:66 msgid "Mail domains" -msgstr "" +msgstr "דמות תחום לדוא״ל" #: mailu/ui/templates/sidebar.html:72 msgid "Go to" -msgstr "" +msgstr "מעבר אל" #: mailu/ui/templates/sidebar.html:76 msgid "Webmail" -msgstr "" +msgstr "דוא״ל בדפדפן" #: mailu/ui/templates/sidebar.html:87 msgid "Website" -msgstr "" +msgstr "אתר" #: mailu/ui/templates/sidebar.html:92 msgid "Help" -msgstr "" +msgstr "עזרה" #: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:98 msgid "Register a domain" -msgstr "" +msgstr "רישום שם תחום" #: mailu/ui/templates/sidebar.html:105 msgid "Sign out" -msgstr "" +msgstr "יציאה" #: mailu/ui/templates/working.html:4 msgid "We are still working on this feature!" -msgstr "" +msgstr "אנחנו עדיין עובדים על היכולת הזאת!" #: mailu/ui/templates/admin/create.html:4 msgid "Add a global administrator" -msgstr "" +msgstr "הוספת מנהל כללי" #: mailu/ui/templates/admin/list.html:4 msgid "Global administrators" -msgstr "" +msgstr "מנהלים כלליים" #: mailu/ui/templates/admin/list.html:9 msgid "Add administrator" -msgstr "" +msgstr "הוספת מנהל" #: mailu/ui/templates/admin/list.html:16 mailu/ui/templates/alias/list.html:18 #: mailu/ui/templates/alternative/list.html:18 @@ -382,12 +383,12 @@ msgstr "" #: mailu/ui/templates/relay/list.html:16 mailu/ui/templates/token/list.html:18 #: mailu/ui/templates/user/list.html:18 msgid "Actions" -msgstr "" +msgstr "פעולות" #: mailu/ui/templates/admin/list.html:17 mailu/ui/templates/alias/list.html:19 #: mailu/ui/templates/manager/list.html:19 mailu/ui/templates/user/list.html:20 msgid "Email" -msgstr "" +msgstr "דוא״ל" #: mailu/ui/templates/admin/list.html:22 mailu/ui/templates/alias/list.html:29 #: mailu/ui/templates/alternative/list.html:25 @@ -396,23 +397,23 @@ msgstr "" #: mailu/ui/templates/relay/list.html:27 mailu/ui/templates/token/list.html:26 #: mailu/ui/templates/user/list.html:31 msgid "Delete" -msgstr "" +msgstr "מחיקה" #: mailu/ui/templates/alias/create.html:4 msgid "Create alias" -msgstr "" +msgstr "יצירת כינוי" #: mailu/ui/templates/alias/edit.html:4 msgid "Edit alias" -msgstr "" +msgstr "עריכת כינוי" #: mailu/ui/templates/alias/list.html:4 msgid "Alias list" -msgstr "" +msgstr "רשימת כינויים" #: mailu/ui/templates/alias/list.html:12 msgid "Add alias" -msgstr "" +msgstr "הוספת כינוי" #: mailu/ui/templates/alias/list.html:22 #: mailu/ui/templates/alternative/list.html:20 @@ -420,254 +421,259 @@ msgstr "" #: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:21 #: mailu/ui/templates/user/list.html:24 msgid "Created" -msgstr "" +msgstr "נוצר" #: mailu/ui/templates/alias/list.html:23 mailu/ui/templates/domain/list.html:23 #: mailu/ui/templates/fetch/list.html:25 mailu/ui/templates/relay/list.html:21 #: mailu/ui/templates/user/list.html:25 msgid "Last edit" -msgstr "" +msgstr "עריכה אחרונה" #: mailu/ui/templates/alias/list.html:28 mailu/ui/templates/domain/list.html:30 #: mailu/ui/templates/fetch/list.html:30 mailu/ui/templates/relay/list.html:26 #: mailu/ui/templates/user/list.html:30 msgid "Edit" -msgstr "" +msgstr "עריכה" #: mailu/ui/templates/alternative/create.html:4 msgid "Create alternative domain" -msgstr "" +msgstr "יצירת שם תחום חלופי" #: mailu/ui/templates/alternative/list.html:4 msgid "Alternative domain list" -msgstr "" +msgstr "רשימת שמות תחום חלופיים" #: mailu/ui/templates/alternative/list.html:12 msgid "Add alternative" -msgstr "" +msgstr "הוספת חלופה" #: mailu/ui/templates/alternative/list.html:19 msgid "Name" -msgstr "" +msgstr "שם" #: mailu/ui/templates/domain/create.html:4 #: mailu/ui/templates/domain/list.html:9 msgid "New domain" -msgstr "" +msgstr "שם תחום חדש" #: mailu/ui/templates/domain/details.html:4 msgid "Domain details" -msgstr "" +msgstr "פרטי שם התחום" #: mailu/ui/templates/domain/details.html:15 msgid "Regenerate keys" -msgstr "" +msgstr "יצירת מפתחות מחדש" #: mailu/ui/templates/domain/details.html:17 msgid "Generate keys" -msgstr "" +msgstr "יצירת מפתחות" #: mailu/ui/templates/domain/details.html:31 msgid "DNS MX entry" -msgstr "" +msgstr "רשומת MX ב־DNS" #: mailu/ui/templates/domain/details.html:35 msgid "DNS SPF entries" -msgstr "" +msgstr "רשומות SPF ב־DNS" #: mailu/ui/templates/domain/details.html:42 msgid "DKIM public key" -msgstr "" +msgstr "מפתח DKIM ציבורי" #: mailu/ui/templates/domain/details.html:46 msgid "DNS DKIM entry" -msgstr "" +msgstr "רשומת DKIM ב־DNS" #: mailu/ui/templates/domain/details.html:50 msgid "DNS DMARC entry" -msgstr "" +msgstr "רשומת DMARC ב־DNS" #: mailu/ui/templates/domain/edit.html:4 msgid "Edit domain" -msgstr "" +msgstr "עריכת שם תחום" #: mailu/ui/templates/domain/list.html:4 msgid "Domain list" -msgstr "" +msgstr "רשימת שמות תחום" #: mailu/ui/templates/domain/list.html:17 msgid "Manage" -msgstr "" +msgstr "ניהול" #: mailu/ui/templates/domain/list.html:19 msgid "Mailbox count" -msgstr "" +msgstr "כמות תיבות דוא״ל" #: mailu/ui/templates/domain/list.html:20 msgid "Alias count" -msgstr "" +msgstr "כמות כינויים" #: mailu/ui/templates/domain/list.html:28 msgid "Details" -msgstr "" +msgstr "פרטים" #: mailu/ui/templates/domain/list.html:35 msgid "Users" -msgstr "" +msgstr "משתמשים" #: mailu/ui/templates/domain/list.html:36 msgid "Aliases" -msgstr "" +msgstr "כינויים" #: mailu/ui/templates/domain/list.html:37 msgid "Managers" -msgstr "" +msgstr "מפקחים" #: mailu/ui/templates/domain/list.html:39 msgid "Alternatives" -msgstr "" +msgstr "חלופות" #: mailu/ui/templates/domain/signup.html:13 msgid "" "In order to register a new domain, you must first setup the\n" " domain zone so that the domain MX points to this server" msgstr "" +"כדי לרשום שם תחום חדש, תחילה עליך להקים את אזור התחום\n" +" ‏(domain zone) כדי שה־MX של שם התחום יפנה לשרת הזה" #: mailu/ui/templates/domain/signup.html:18 msgid "" "If you do not know how to setup an MX record for your DNS " "zone,\n" -" please contact your DNS provider or administrator. Also, please wait " -"a\n" +" please contact your DNS provider or administrator. Also, please wait a\n" " couple minutes after the MX is set so the local server " "cache\n" " expires." msgstr "" +"אם לא ברור לך איך להקים רשומת MX עבור אזור ה־DNS שלך,\n" +" נא ליצור קשר עם ספק ה־ DNS או ההנהלה שלך. כמו כן, נא להמתין מספר דקות\n" +" לאחר הגדרת ה־MX כדי לאפשר לתוקף המטמון המקורי בשרת\n" +" לפוג." #: mailu/ui/templates/fetch/create.html:4 msgid "Add a fetched account" -msgstr "" +msgstr "הוספת חשבון נמשך" #: mailu/ui/templates/fetch/edit.html:4 msgid "Update a fetched account" -msgstr "" +msgstr "עדכון חשבון שנמשך" #: mailu/ui/templates/fetch/list.html:12 msgid "Add an account" -msgstr "" +msgstr "הוספת חשבון" #: mailu/ui/templates/fetch/list.html:19 msgid "Endpoint" -msgstr "" +msgstr "נקודת גישה" #: mailu/ui/templates/fetch/list.html:21 msgid "Keep emails" -msgstr "" +msgstr "לשמור על ההודעות" #: mailu/ui/templates/fetch/list.html:22 msgid "Last check" -msgstr "" +msgstr "בדיקה אחרונה" #: mailu/ui/templates/fetch/list.html:35 msgid "yes" -msgstr "" +msgstr "כן" #: mailu/ui/templates/fetch/list.html:35 msgid "no" -msgstr "" +msgstr "לא" #: mailu/ui/templates/manager/create.html:4 msgid "Add a manager" -msgstr "" +msgstr "הוספת מנהל" #: mailu/ui/templates/manager/list.html:4 msgid "Manager list" -msgstr "" +msgstr "רשימת מנהלים" #: mailu/ui/templates/manager/list.html:12 msgid "Add manager" -msgstr "" +msgstr "הוספת מנהל" #: mailu/ui/templates/relay/create.html:4 msgid "New relay domain" -msgstr "" +msgstr "שם תחום מועבר" #: mailu/ui/templates/relay/edit.html:4 msgid "Edit relayd domain" -msgstr "" +msgstr "עריכת שמות תחום מועברים" #: mailu/ui/templates/relay/list.html:4 msgid "Relayed domain list" -msgstr "" +msgstr "רשימת שמות תחום מועברים" #: mailu/ui/templates/relay/list.html:9 msgid "New relayed domain" -msgstr "" +msgstr "שם תחום מועבר חדש" #: mailu/ui/templates/token/create.html:4 msgid "Create an authentication token" -msgstr "" +msgstr "יצירת אסימון אימות" #: mailu/ui/templates/token/list.html:12 msgid "New token" -msgstr "" +msgstr "אסימון חדש" #: mailu/ui/templates/user/create.html:4 msgid "New user" -msgstr "" +msgstr "משתמש חדש" #: mailu/ui/templates/user/create.html:15 msgid "General" -msgstr "" +msgstr "כללי" #: mailu/ui/templates/user/create.html:22 msgid "Features and quotas" -msgstr "" +msgstr "יכולות ומיכסות" #: mailu/ui/templates/user/edit.html:4 msgid "Edit user" -msgstr "" +msgstr "עריכת משתמש" #: mailu/ui/templates/user/forward.html:4 msgid "Forward emails" -msgstr "" +msgstr "העברת הודעות" #: mailu/ui/templates/user/list.html:4 msgid "User list" -msgstr "" +msgstr "רשימת משתמשים" #: mailu/ui/templates/user/list.html:12 msgid "Add user" -msgstr "" +msgstr "הוספת משתמש" #: mailu/ui/templates/user/list.html:19 mailu/ui/templates/user/settings.html:4 msgid "User settings" -msgstr "" +msgstr "הגדרות משתמש" #: mailu/ui/templates/user/list.html:21 msgid "Features" -msgstr "" +msgstr "יכולות" #: mailu/ui/templates/user/password.html:4 msgid "Password update" -msgstr "" +msgstr "עדכון סיסמה" #: mailu/ui/templates/user/reply.html:4 msgid "Automatic reply" -msgstr "" +msgstr "מענה אוטומטי" #: mailu/ui/templates/user/settings.html:22 msgid "Auto-forward" -msgstr "" +msgstr "העברה אוטומטית" #: mailu/ui/templates/user/signup_domain.html:8 msgid "pick a domain for the new account" -msgstr "" +msgstr "נא לבחור שם תחום לחשבון החדש" #: mailu/ui/templates/user/signup_domain.html:14 msgid "Domain" -msgstr "" +msgstr "שם תחום" #: mailu/ui/templates/user/signup_domain.html:15 msgid "Available slots" -msgstr "" +msgstr "מקומות פנויים" diff --git a/core/admin/mailu/ui/templates/admin/create.html b/core/admin/mailu/ui/templates/admin/create.html index 8d3a7b58..6c2413bc 100644 --- a/core/admin/mailu/ui/templates/admin/create.html +++ b/core/admin/mailu/ui/templates/admin/create.html @@ -5,7 +5,7 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %}
{{ form.hidden_tag() }} {{ macros.form_field(form.admin, class_='mailselect') }} diff --git a/core/admin/mailu/ui/templates/admin/list.html b/core/admin/mailu/ui/templates/admin/list.html index 72b5a1fa..f2f5d229 100644 --- a/core/admin/mailu/ui/templates/admin/list.html +++ b/core/admin/mailu/ui/templates/admin/list.html @@ -5,24 +5,28 @@ {% endblock %} {% block main_action %} - + {% trans %}Add administrator{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Email{% endtrans %} - -{% for admin in admins %} - - - - - {{ admin }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Email{% endtrans %} + + + + {% for admin in admins %} + + + + + {{ admin }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/alias/create.html b/core/admin/mailu/ui/templates/alias/create.html index 38d7e7e5..2079d191 100644 --- a/core/admin/mailu/ui/templates/alias/create.html +++ b/core/admin/mailu/ui/templates/alias/create.html @@ -9,10 +9,10 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %} {{ form.hidden_tag() }} - {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} + {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} {{ macros.form_field(form.wildcard) }} {{ macros.form_field(form.destination, class_='mailselect') }} {{ macros.form_field(form.comment) }} diff --git a/core/admin/mailu/ui/templates/alias/list.html b/core/admin/mailu/ui/templates/alias/list.html index 29766b25..e8ddc862 100644 --- a/core/admin/mailu/ui/templates/alias/list.html +++ b/core/admin/mailu/ui/templates/alias/list.html @@ -9,31 +9,35 @@ {% endblock %} {% block main_action %} -{% trans %}Add alias{% endtrans %} +{% trans %}Add alias{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Email{% endtrans %} - {% trans %}Destination{% endtrans %} - {% trans %}Comment{% endtrans %} - {% trans %}Created{% endtrans %} - {% trans %}Last edit{% endtrans %} - -{% for alias in domain.aliases %} - - -   - - - {{ alias }} - {{ alias.destination|join(', ') or '-' }} - {{ alias.comment or '' }} - {{ alias.created_at }} - {{ alias.updated_at or '' }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Email{% endtrans %} + {% trans %}Destination{% endtrans %} + {% trans %}Comment{% endtrans %} + {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} + + + + {% for alias in domain.aliases %} + + +   + + + {{ alias }} + {{ alias.destination|join(', ') or '-' }} + {{ alias.comment or '' }} + {{ alias.created_at }} + {{ alias.updated_at or '' }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/alternative/list.html b/core/admin/mailu/ui/templates/alternative/list.html index 56e7565b..f123eb9f 100644 --- a/core/admin/mailu/ui/templates/alternative/list.html +++ b/core/admin/mailu/ui/templates/alternative/list.html @@ -9,24 +9,28 @@ {% endblock %} {% block main_action %} -{% trans %}Add alternative{% endtrans %} +{% trans %}Add alternative{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Name{% endtrans %} - {% trans %}Created{% endtrans %} - -{% for alternative in domain.alternatives %} - - - - - {{ alternative }} - {{ alternative.created_at }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Name{% endtrans %} + {% trans %}Created{% endtrans %} + + + + {% for alternative in domain.alternatives %} + + + + + {{ alternative }} + {{ alternative.created_at }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/announcement.html b/core/admin/mailu/ui/templates/announcement.html index 7dd34d3f..acdbde1a 100644 --- a/core/admin/mailu/ui/templates/announcement.html +++ b/core/admin/mailu/ui/templates/announcement.html @@ -5,7 +5,7 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %} {{ form.hidden_tag() }} {{ macros.form_field(form.announcement_subject) }} diff --git a/core/admin/mailu/ui/templates/base.html b/core/admin/mailu/ui/templates/base.html index 74d5653c..89695e50 100644 --- a/core/admin/mailu/ui/templates/base.html +++ b/core/admin/mailu/ui/templates/base.html @@ -8,44 +8,58 @@ Mailu-Admin - {{ config["SITENAME"] }} - +
-
- -
-
diff --git a/core/admin/mailu/ui/templates/client.html b/core/admin/mailu/ui/templates/client.html index 81bee135..668621af 100644 --- a/core/admin/mailu/ui/templates/client.html +++ b/core/admin/mailu/ui/templates/client.html @@ -1,3 +1,5 @@ + + {% extends "base.html" %} {% block title %} @@ -9,8 +11,7 @@ configure your email client {% endblock %} {% block content %} -{% call macros.box(title="Incoming mail") %} - +{% call macros.table(title="Incoming mail", datatable=False) %} @@ -33,11 +34,9 @@ configure your email client -
{% trans %}Mail protocol{% endtrans %}
*******
{% endcall %} -{% call macros.box(title="Outgoing mail") %} - +{% call macros.table(title="Outgoing mail", datatable=False) %} @@ -60,6 +59,5 @@ configure your email client -
{% trans %}Mail protocol{% endtrans %}
*******
{% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/confirm.html b/core/admin/mailu/ui/templates/confirm.html index 3b8a451f..d0f6acf3 100644 --- a/core/admin/mailu/ui/templates/confirm.html +++ b/core/admin/mailu/ui/templates/confirm.html @@ -9,7 +9,7 @@ {% endblock %} {% block content %} -{% call macros.box(theme="warning") %} +{% call macros.card(theme="warning") %}

{% trans action %}You are about to {{ action }}. Please confirm your action.{% endtrans %}

{{ macros.form(form) }} {% endcall %} diff --git a/core/admin/mailu/ui/templates/domain/create.html b/core/admin/mailu/ui/templates/domain/create.html index d67e3e89..3fdd262a 100644 --- a/core/admin/mailu/ui/templates/domain/create.html +++ b/core/admin/mailu/ui/templates/domain/create.html @@ -5,13 +5,13 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %} {{ form.hidden_tag() }} {{ macros.form_field(form.name) }} {{ macros.form_fields((form.max_users, form.max_aliases)) }} {{ macros.form_field(form.max_quota_bytes, step=1000000000, max=50000000000, - prepend=''+((form.max_quota_bytes.data//1000000000).__str__() if form.max_quota_bytes.data else '∞')+' GiB', + prepend=''+((form.max_quota_bytes.data//1000000000).__str__() if form.max_quota_bytes.data else '∞')+' GiB', oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }} {{ macros.form_field(form.signup_enabled) }} {{ macros.form_field(form.comment) }} diff --git a/core/admin/mailu/ui/templates/domain/details.html b/core/admin/mailu/ui/templates/domain/details.html index 5822ffbc..0560d5e0 100644 --- a/core/admin/mailu/ui/templates/domain/details.html +++ b/core/admin/mailu/ui/templates/domain/details.html @@ -10,7 +10,7 @@ {% block main_action %} {% if current_user.global_admin %} - + {% if domain.dkim_publickey %} {% trans %}Regenerate keys{% endtrans %} {% else %} @@ -21,7 +21,7 @@ {% endblock %} {% block content %} -{% call macros.table() %} +{% call macros.table(datatable=False) %} {% set hostname = config["HOSTNAMES"].split(",")[0] %} {% trans %}Domain name{% endtrans %} diff --git a/core/admin/mailu/ui/templates/domain/list.html b/core/admin/mailu/ui/templates/domain/list.html index 2431faa5..c82647df 100644 --- a/core/admin/mailu/ui/templates/domain/list.html +++ b/core/admin/mailu/ui/templates/domain/list.html @@ -6,46 +6,50 @@ {% block main_action %} {% if current_user.global_admin %} -{% trans %}New domain{% endtrans %} +{% trans %}New domain{% endtrans %} {% endif %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Manage{% endtrans %} - {% trans %}Domain name{% endtrans %} - {% trans %}Mailbox count{% endtrans %} - {% trans %}Alias count{% endtrans %} - {% trans %}Comment{% endtrans %} - {% trans %}Created{% endtrans %} - {% trans %}Last edit{% endtrans %} - -{% for domain in current_user.get_managed_domains() %} - - -   - {% if current_user.global_admin %} -   -   - {% endif %} - - -   -   -   - {% if current_user.global_admin %} -   - {% endif %} - - {{ domain.name }} - {{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }} - {{ domain.aliases | count }} / {{ '∞' if domain.max_aliases == -1 else domain.max_aliases }} - {{ domain.comment or '' }} - {{ domain.created_at }} - {{ domain.updated_at or '' }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Manage{% endtrans %} + {% trans %}Domain name{% endtrans %} + {% trans %}Mailbox count{% endtrans %} + {% trans %}Alias count{% endtrans %} + {% trans %}Comment{% endtrans %} + {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} + + + + {% for domain in current_user.get_managed_domains() %} + + +   + {% if current_user.global_admin %} +   +   + {% endif %} + + +   +   +   + {% if current_user.global_admin %} +   + {% endif %} + + {{ domain.name }} + {{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }} + {{ domain.aliases | count }} / {{ '∞' if domain.max_aliases == -1 else domain.max_aliases }} + {{ domain.comment or '' }} + {{ domain.created_at }} + {{ domain.updated_at or '' }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/domain/signup.html b/core/admin/mailu/ui/templates/domain/signup.html index c8a52f6c..e9dbd75f 100644 --- a/core/admin/mailu/ui/templates/domain/signup.html +++ b/core/admin/mailu/ui/templates/domain/signup.html @@ -9,7 +9,7 @@ {{ form.hidden_tag() }} - {% call macros.box(title="Requirements") %} + {% call macros.card(title="Requirements") %}

{% trans %}In order to register a new domain, you must first setup the domain zone so that the domain MX points to this server{% endtrans %} ({{ config["HOSTNAMES"].split(",")[0] }}). @@ -22,9 +22,9 @@

{% endcall %} - {% call macros.box() %} + {% call macros.card() %} {% if form.localpart %} - {{ macros.form_fields((form.localpart, form.name), append='@') }} + {{ macros.form_fields((form.localpart, form.name), append='@') }} {{ macros.form_fields((form.pw, form.pw2)) }} {% else %} {{ macros.form_field(form.name) }} diff --git a/core/admin/mailu/ui/templates/fetch/create.html b/core/admin/mailu/ui/templates/fetch/create.html index bc69fc44..1b5bc194 100644 --- a/core/admin/mailu/ui/templates/fetch/create.html +++ b/core/admin/mailu/ui/templates/fetch/create.html @@ -11,18 +11,18 @@ {% block content %} {{ form.hidden_tag() }} - {% call macros.box(title="Remote server") %} + {% call macros.card(title="Remote server") %} {{ macros.form_field(form.protocol) }} {{ macros.form_fields((form.host, form.port)) }} {{ macros.form_field(form.tls) }} {% endcall %} - {% call macros.box(title="Authentication") %} + {% call macros.card(title="Authentication") %} {{ macros.form_field(form.username) }} {{ macros.form_field(form.password) }} {% endcall %} - {% call macros.box(title="Settings") %} + {% call macros.card(title="Settings") %} {{ macros.form_field(form.keep) }} {% endcall %} diff --git a/core/admin/mailu/ui/templates/fetch/list.html b/core/admin/mailu/ui/templates/fetch/list.html index 07ab5ad8..77f66bb1 100644 --- a/core/admin/mailu/ui/templates/fetch/list.html +++ b/core/admin/mailu/ui/templates/fetch/list.html @@ -9,35 +9,39 @@ {% endblock %} {% block main_action %} -{% trans %}Add an account{% endtrans %} +{% trans %}Add an account{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Endpoint{% endtrans %} - {% trans %}Username{% endtrans %} - {% trans %}Keep emails{% endtrans %} - {% trans %}Last check{% endtrans %} - {% trans %}Status{% endtrans %} - {% trans %}Created{% endtrans %} - {% trans %}Last edit{% endtrans %} - -{% for fetch in user.fetches %} - - -   - - - {{ fetch.protocol }}{{ 's' if fetch.tls else '' }}://{{ fetch.host }}:{{ fetch.port }} - {{ fetch.username }} - {% if fetch.keep %}{% trans %}yes{% endtrans %}{% else %}{% trans %}no{% endtrans %}{% endif %} - {{ fetch.last_check or '-' }} - {{ fetch.error or '-' }} - {{ fetch.created_at }} - {{ fetch.updated_at or '' }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Endpoint{% endtrans %} + {% trans %}Username{% endtrans %} + {% trans %}Keep emails{% endtrans %} + {% trans %}Last check{% endtrans %} + {% trans %}Status{% endtrans %} + {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} + + + + {% for fetch in user.fetches %} + + +   + + + {{ fetch.protocol }}{{ 's' if fetch.tls else '' }}://{{ fetch.host }}:{{ fetch.port }} + {{ fetch.username }} + {% if fetch.keep %}{% trans %}yes{% endtrans %}{% else %}{% trans %}no{% endtrans %}{% endif %} + {{ fetch.last_check or '-' }} + {{ fetch.error or '-' }} + {{ fetch.created_at }} + {{ fetch.updated_at or '' }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/form.html b/core/admin/mailu/ui/templates/form.html index 25d7e83c..0e65ce33 100644 --- a/core/admin/mailu/ui/templates/form.html +++ b/core/admin/mailu/ui/templates/form.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -{% call macros.box() %} +{% call macros.card() %} {{ macros.form(form) }} {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/macros.html b/core/admin/mailu/ui/templates/macros.html index ec4cf6e4..d1715c7a 100644 --- a/core/admin/mailu/ui/templates/macros.html +++ b/core/admin/mailu/ui/templates/macros.html @@ -37,9 +37,10 @@ {{ field.label if label else '' }} {% else %} {{ field.label if label else '' }}{{ form_field_errors(field) }} - {% if prepend or append %}
{% endif %} - {{ prepend|safe }}{{ field(class_="form-control " + class_, **kwargs) }}{{ append|safe }} - {% if prepend or append %}
{% endif %} + {% if prepend %}
{% endif %} + {% if append %}
{% endif %} + {{ prepend|safe }}{{ field(class_="form-control " + class_, **kwargs) }}{{ append|safe }} + {% if prepend or append %}
{% endif %} {% endif %} {% endmacro %} @@ -64,18 +65,18 @@ {% endmacro %} -{% macro box(title=None, theme="primary", header=True) %} +{% macro card(title=None, theme="primary", header=True) %}
-
+
{% if header %} -
+
{% if title %} -

{{ title }}

+

{{ title }}

{% endif %}
{% endif %} -
+
{{ caller() }}
@@ -83,15 +84,20 @@
{% endmacro %} -{% macro table(theme="primary") %} +{% macro table(title=None, theme="primary", datatable=True) %}
-
- - +
+
+ {% if title %} +

{{ title }}

+ {% endif %} +
+
+
{{ caller() }} - -
+ +
diff --git a/core/admin/mailu/ui/templates/manager/create.html b/core/admin/mailu/ui/templates/manager/create.html index e3911642..bc5e6ca9 100644 --- a/core/admin/mailu/ui/templates/manager/create.html +++ b/core/admin/mailu/ui/templates/manager/create.html @@ -9,7 +9,7 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %}
{{ form.hidden_tag() }} {{ macros.form_field(form.manager, class_='mailselect') }} diff --git a/core/admin/mailu/ui/templates/manager/list.html b/core/admin/mailu/ui/templates/manager/list.html index d818a876..9a78e3ca 100644 --- a/core/admin/mailu/ui/templates/manager/list.html +++ b/core/admin/mailu/ui/templates/manager/list.html @@ -9,15 +9,18 @@ {% endblock %} {% block main_action %} -{% trans %}Add manager{% endtrans %} +{% trans %}Add manager{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Email{% endtrans %} - + + + {% trans %}Actions{% endtrans %} + {% trans %}Email{% endtrans %} + + + {% for manager in domain.managers %} @@ -26,5 +29,6 @@ {{ manager }} {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/relay/list.html b/core/admin/mailu/ui/templates/relay/list.html index bb00b1d9..6c8c9196 100644 --- a/core/admin/mailu/ui/templates/relay/list.html +++ b/core/admin/mailu/ui/templates/relay/list.html @@ -6,32 +6,36 @@ {% block main_action %} {% if current_user.global_admin %} -{% trans %}New relayed domain{% endtrans %} +{% trans %}New relayed domain{% endtrans %} {% endif %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Domain name{% endtrans %} - {% trans %}Remote host{% endtrans %} - {% trans %}Comment{% endtrans %} - {% trans %}Created{% endtrans %} - {% trans %}Last edit{% endtrans %} - -{% for relay in relays %} - - -  -   - - {{ relay.name }} - {{ relay.smtp or '-' }} - {{ relay.comment or '' }} - {{ relay.created_at }} - {{ relay.updated_at or '' }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Domain name{% endtrans %} + {% trans %}Remote host{% endtrans %} + {% trans %}Comment{% endtrans %} + {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} + + + + {% for relay in relays %} + + +   +   + + {{ relay.name }} + {{ relay.smtp or '-' }} + {{ relay.comment or '' }} + {{ relay.created_at }} + {{ relay.updated_at or '' }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/sidebar.html b/core/admin/mailu/ui/templates/sidebar.html index 78be75d5..0fdae9db 100644 --- a/core/admin/mailu/ui/templates/sidebar.html +++ b/core/admin/mailu/ui/templates/sidebar.html @@ -1,120 +1,144 @@ - + + {% if config["WEBMAIL"] != "none" %} + + {% endif %} + + + + {% if config['DOMAIN_REGISTRATION'] %} + + {% endif %} + {% if current_user.is_authenticated %} + + {% else %} + + {% if signup_domains %} + + {% endif %} + {% endif %} + + +
diff --git a/core/admin/mailu/ui/templates/token/list.html b/core/admin/mailu/ui/templates/token/list.html index ad560e6a..09d0fe76 100644 --- a/core/admin/mailu/ui/templates/token/list.html +++ b/core/admin/mailu/ui/templates/token/list.html @@ -9,26 +9,30 @@ {% endblock %} {% block main_action %} -{% trans %}New token{% endtrans %} +{% trans %}New token{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}Comment{% endtrans %} - {% trans %}Authorized IP{% endtrans %} - {% trans %}Created{% endtrans %} - -{% for token in user.tokens %} - - - - - {{ token.comment }} - {{ token.ip or "any" }} - {{ token.created_at }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}Comment{% endtrans %} + {% trans %}Authorized IP{% endtrans %} + {% trans %}Created{% endtrans %} + + + + {% for token in user.tokens %} + + + + + {{ token.comment }} + {{ token.ip or "any" }} + {{ token.created_at }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/user/create.html b/core/admin/mailu/ui/templates/user/create.html index ed7b9884..886cbb88 100644 --- a/core/admin/mailu/ui/templates/user/create.html +++ b/core/admin/mailu/ui/templates/user/create.html @@ -12,17 +12,17 @@ {{ form.hidden_tag() }} - {% call macros.box(_("General")) %} - {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} + {% call macros.card(_("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 %} - {% call macros.box(_("Features and quotas"), theme="success") %} + {% call macros.card(_("Features and quotas"), theme="success") %} {{ macros.form_field(form.quota_bytes, step=1000000000, max=(max_quota_bytes or domain.max_quota_bytes or 50000000000), - prepend=''+((form.quota_bytes.data//1000000000).__str__() if form.quota_bytes.data else '∞')+' GiB', + prepend=''+((form.quota_bytes.data//1000000000).__str__() if form.quota_bytes.data else '∞')+' GiB', oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }} {{ macros.form_field(form.enable_imap) }} {{ macros.form_field(form.enable_pop) }} diff --git a/core/admin/mailu/ui/templates/user/forward.html b/core/admin/mailu/ui/templates/user/forward.html index 769d439d..6059f0ed 100644 --- a/core/admin/mailu/ui/templates/user/forward.html +++ b/core/admin/mailu/ui/templates/user/forward.html @@ -9,7 +9,7 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %} {{ form.hidden_tag() }} {{ macros.form_field(form.forward_enabled, diff --git a/core/admin/mailu/ui/templates/user/list.html b/core/admin/mailu/ui/templates/user/list.html index 2aff662f..cfec6276 100644 --- a/core/admin/mailu/ui/templates/user/list.html +++ b/core/admin/mailu/ui/templates/user/list.html @@ -9,42 +9,46 @@ {% endblock %} {% block main_action %} -{% trans %}Add user{% endtrans %} +{% trans %}Add user{% endtrans %} {% endblock %} {% block content %} {% call macros.table() %} - - {% trans %}Actions{% endtrans %} - {% trans %}User settings{% endtrans %} - {% trans %}Email{% endtrans %} - {% trans %}Features{% endtrans %} - {% trans %}Quota{% endtrans %} - {% trans %}Comment{% endtrans %} - {% trans %}Created{% endtrans %} - {% trans %}Last edit{% endtrans %} - -{% for user in domain.users %} - - -   - - - -   -   -   - - {{ user }} - - {% if user.enable_imap %}imap{% endif %} - {% if user.enable_pop %}pop3{% endif %} - - {{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} - {{ user.comment or '-' }} - {{ user.created_at }} - {{ user.updated_at or '' }} - -{% endfor %} + + + {% trans %}Actions{% endtrans %} + {% trans %}User settings{% endtrans %} + {% trans %}Email{% endtrans %} + {% trans %}Features{% endtrans %} + {% trans %}Quota{% endtrans %} + {% trans %}Comment{% endtrans %} + {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} + + + + {% for user in domain.users %} + + +   + + + +   +   +   + + {{ user }} + + {% if user.enable_imap %}imap{% endif %} + {% if user.enable_pop %}pop3{% endif %} + + {{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} + {{ user.comment or '-' }} + {{ user.created_at }} + {{ user.updated_at or '' }} + + {% endfor %} + {% endcall %} {% endblock %} diff --git a/core/admin/mailu/ui/templates/user/reply.html b/core/admin/mailu/ui/templates/user/reply.html index 7225a178..74c604fa 100644 --- a/core/admin/mailu/ui/templates/user/reply.html +++ b/core/admin/mailu/ui/templates/user/reply.html @@ -9,12 +9,12 @@ {% endblock %} {% block content %} -{% call macros.box() %} +{% call macros.card() %} {{ form.hidden_tag() }} {{ macros.form_field(form.reply_enabled, onchange="if(this.checked){$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').removeAttr('readonly')} - else{$('#reply_subject,#reply_body,#reply_enddate').attr('readonly', '')}") }} + else{$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').attr('readonly', '')}") }} {{ macros.form_field(form.reply_subject, **{("rw" if user.reply_enabled else "readonly"): ""}) }} {{ macros.form_field(form.reply_body, rows=10, diff --git a/core/admin/mailu/ui/templates/user/settings.html b/core/admin/mailu/ui/templates/user/settings.html index b6ade695..9aa672ca 100644 --- a/core/admin/mailu/ui/templates/user/settings.html +++ b/core/admin/mailu/ui/templates/user/settings.html @@ -12,18 +12,18 @@ {{ form.hidden_tag() }} - {% call macros.box(title=_("Displayed name")) %} + {% call macros.card(title=_("Displayed name")) %} {{ macros.form_field(form.displayed_name) }} {% endcall %} - {% call macros.box(title=_("Antispam")) %} + {% call macros.card(title=_("Antispam")) %} {{ macros.form_field(form.spam_enabled) }} {{ macros.form_field(form.spam_threshold, step=1, max=100, - prepend=''+form.spam_threshold.data.__str__()+' / 100', + prepend=''+form.spam_threshold.data.__str__()+' / 100', oninput='$("#threshold").text(this.value);') }} {% endcall %} - {% call macros.box(title=_("Auto-forward")) %} + {% call macros.card(title=_("Auto-forward")) %} {{ macros.form_field(form.forward_enabled, onchange="if(this.checked){$('#forward_destination,#forward_keep').removeAttr('disabled')} else{$('#forward_destination,#forward_keep').attr('disabled', '')}") }} diff --git a/core/admin/mailu/ui/templates/user/signup.html b/core/admin/mailu/ui/templates/user/signup.html index f540d660..1157cfa3 100644 --- a/core/admin/mailu/ui/templates/user/signup.html +++ b/core/admin/mailu/ui/templates/user/signup.html @@ -11,8 +11,8 @@ {% block content %} {{ form.hidden_tag() }} - {% call macros.box() %} - {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} + {% call macros.card() %} + {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} {{ macros.form_fields((form.pw, form.pw2)) }} {% if form.captcha %} {{ macros.form_field(form.captcha) }} diff --git a/core/admin/mailu/ui/views/__init__.py b/core/admin/mailu/ui/views/__init__.py index e7ae8b35..5488c82f 100644 --- a/core/admin/mailu/ui/views/__init__.py +++ b/core/admin/mailu/ui/views/__init__.py @@ -1,4 +1,4 @@ __all__ = [ 'admins', 'aliases', 'alternatives', 'base', 'domains', 'fetches', - 'managers', 'users', 'relays', 'tokens' + 'managers', 'users', 'relays', 'tokens', 'languages' ] diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index 719d3844..f394ce7d 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -74,6 +74,8 @@ def domain_details(domain_name): def domain_genkeys(domain_name): domain = models.Domain.query.get(domain_name) or flask.abort(404) domain.generate_dkim_key() + models.db.session.add(domain) + models.db.session.commit() return flask.redirect( flask.url_for(".domain_details", domain_name=domain_name)) diff --git a/core/admin/mailu/ui/views/languages.py b/core/admin/mailu/ui/views/languages.py new file mode 100644 index 00000000..9fb87d5c --- /dev/null +++ b/core/admin/mailu/ui/views/languages.py @@ -0,0 +1,9 @@ +from mailu.ui import ui, forms, access + +import flask + + +@ui.route('/language/', methods=['GET']) +def set_language(language=None): + flask.session['language'] = language + return flask.redirect(flask.url_for('.user_settings')) diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index 02150754..b1279d9e 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -46,8 +46,16 @@ babel = flask_babel.Babel() @babel.localeselector def get_locale(): """ selects locale for translation """ - translations = [str(translation) for translation in babel.list_translations()] - return flask.request.accept_languages.best_match(translations) + translations = list(map(str, babel.list_translations())) + flask.session['available_languages'] = translations + + try: + language = flask.session['language'] + except KeyError: + language = flask.request.accept_languages.best_match(translations) + flask.session['language'] = language + + return language # Proxy fixer diff --git a/core/admin/package.json b/core/admin/package.json index a1107c69..d45dcb94 100644 --- a/core/admin/package.json +++ b/core/admin/package.json @@ -2,34 +2,30 @@ "name": "mailu", "version": "1.0.0", "description": "Mailu admin assets", - "main": "assest/index.js", + "main": "assets/index.js", + "directories": { + "lib": "lib" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { - "@babel/core": "^7.4.4", - "@babel/preset-env": "^7.4.4", - "admin-lte": "^2.4.10", - "babel-loader": "^8.0.5", - "bootstrap": "^3.4.1", - "css-loader": "^2.1.1", - "expose-loader": "^0.7.5", - "file-loader": "^3.0.1", - "font-awesome": "^4.7.0", - "font-awesome-loader": "^1.0.2", - "jQuery": "^1.7.4", - "less": "^3.9.0", - "less-loader": "^5.0.0", - "mini-css-extract-plugin": "^0.6.0", - "node-sass": "^4.12.0", - "popper.js": "^1.15.0", - "sass-loader": "^7.1.0", - "select2": "^4.0.7-rc.0", - "style-loader": "^0.23.1", - "url-loader": "^1.1.2", - "webpack": "^4.30.0", - "webpack-cli": "^3.3.2" + "@babel/core": "^7.15.0", + "@babel/preset-env": "^7.15.0", + "admin-lte": "^3.1.0", + "babel-loader": "^8.2.2", + "css-loader": "^6.2.0", + "expose-loader": "^3.0.0", + "jquery": "^3.6.0", + "less": "^4.1.1", + "less-loader": "^10.0.1", + "mini-css-extract-plugin": "^2.2.0", + "node-sass": "^6.0.1", + "sass-loader": "^12.1.0", + "select2": "^4.0.13", + "webpack": "^5.50.0", + "webpack-cli": "^4.7.2" } } diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index c4663557..88ff2981 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -5,7 +5,7 @@ bcrypt==3.1.6 blinker==1.4 cffi==1.12.3 Click==7.0 -cryptography==3.2 +cryptography==3.4.7 decorator==4.4.0 dnspython==1.16.0 dominate==2.3.5 @@ -25,7 +25,7 @@ idna==2.8 infinity==1.4 intervals==0.8.1 itsdangerous==1.1.0 -Jinja2==2.10.1 +Jinja2==2.11.3 limits==1.3 Mako==1.0.9 MarkupSafe==1.1.1 @@ -36,11 +36,11 @@ passlib==1.7.4 psycopg2==2.8.2 pycparser==2.19 Pygments==2.8.1 -pyOpenSSL==19.0.0 +pyOpenSSL==20.0.1 python-dateutil==2.8.0 python-editor==1.0.4 pytz==2019.1 -PyYAML==5.1 +PyYAML==5.4.1 redis==3.2.1 #alpine3:12 provides six==1.15.0 #six==1.12.0 diff --git a/core/admin/start.py b/core/admin/start.py index 2c925e01..0eff3bbe 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -19,7 +19,8 @@ if account is not None and domain is not None and password is not None: os.system("flask mailu admin %s %s '%s' --mode %s" % (account, domain, password, mode)) start_command="".join([ - "gunicorn -w 4 -b :80 ", + "gunicorn --threads ", str(os.cpu_count()), + " -b :80 ", "--access-logfile - " if (log.root.level<=log.INFO) else "", "--error-logfile - ", "--preload ", diff --git a/core/admin/webpack.config.js b/core/admin/webpack.config.js index 73630439..3e823fa9 100644 --- a/core/admin/webpack.config.js +++ b/core/admin/webpack.config.js @@ -30,19 +30,23 @@ module.exports = { test: /\.css$/, use: [css.loader, 'css-loader'] }, - { - test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/, - use: ['url-loader'] - }, { // Exposes jQuery for use outside Webpack build test: require.resolve('jquery'), use: [{ loader: 'expose-loader', - options: 'jQuery' - }, { - loader: 'expose-loader', - options: '$' + options: { + exposes: [ + { + globalName: '$', + override: true, + }, + { + globalName: 'jQuery', + override: true, + }, + ] + }, }] } ] diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index e1c20eff..22145bde 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.13 +ARG DISTRO=alpine:3.14 FROM $DISTRO as builder WORKDIR /tmp RUN apk add git build-base automake autoconf libtool dovecot-dev xapian-core-dev icu-dev diff --git a/core/dovecot/conf/ham.script b/core/dovecot/conf/ham.script index aa25cefb..910df8e4 100755 --- a/core/dovecot/conf/ham.script +++ b/core/dovecot/conf/ham.script @@ -1,5 +1,12 @@ #!/bin/bash -tee >(rspamc -h {{ ANTISPAM_WEBUI_ADDRESS }} -P mailu learn_ham /dev/stdin) \ - >(rspamc -h {{ ANTISPAM_WEBUI_ADDRESS }} -P mailu -f 11 fuzzy_del /dev/stdin) \ - | rspamc -h {{ ANTISPAM_WEBUI_ADDRESS }} -P mailu -f 13 fuzzy_add /dev/stdin +RSPAMD_HOST="$(getent hosts {{ ANTISPAM_WEBUI_ADDRESS }}|cut -d\ -f1)" +if [[ $? -ne 0 ]] +then + echo "Failed to lookup {{ ANTISPAM_WEBUI_ADDRESS }}" >&2 + exit 1 +fi + +tee >(rspamc -h $RSPAMD_HOST -P mailu learn_ham /dev/stdin) \ + >(rspamc -h $RSPAMD_HOST -P mailu -f 11 fuzzy_del /dev/stdin) \ + | rspamc -h $RSPAMD_HOST -P mailu -f 13 fuzzy_add /dev/stdin diff --git a/core/dovecot/conf/spam.script b/core/dovecot/conf/spam.script index 05a3832d..e7d20427 100755 --- a/core/dovecot/conf/spam.script +++ b/core/dovecot/conf/spam.script @@ -1,5 +1,13 @@ #!/bin/bash -tee >(rspamc -h {{ ANTISPAM_WEBUI_ADDRESS }} -P mailu learn_spam /dev/stdin) \ - >(rspamc -h {{ ANTISPAM_WEBUI_ADDRESS }} -P mailu -f 13 fuzzy_del /dev/stdin) \ - | rspamc -h {{ ANTISPAM_WEBUI_ADDRESS }} -P mailu -f 11 fuzzy_add /dev/stdin +RSPAMD_HOST="$(getent hosts {{ ANTISPAM_WEBUI_ADDRESS }}|cut -d\ -f1)" +if [[ $? -ne 0 ]] +then + echo "Failed to lookup {{ ANTISPAM_WEBUI_ADDRESS }}" >&2 + exit 1 +fi + + +tee >(rspamc -h $RSPAMD_HOST -P mailu learn_spam /dev/stdin) \ + >(rspamc -h $RSPAMD_HOST -P mailu -f 13 fuzzy_del /dev/stdin) \ + | rspamc -h $RSPAMD_HOST -P mailu -f 11 fuzzy_add /dev/stdin diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index 2bc1cfd1..1906ed31 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images RUN apk add --no-cache \ diff --git a/core/nginx/conf/dhparam.pem b/core/nginx/conf/dhparam.pem index 3cf0fcbc..4f25f663 100644 --- a/core/nginx/conf/dhparam.pem +++ b/core/nginx/conf/dhparam.pem @@ -1,13 +1,11 @@ -----BEGIN DH PARAMETERS----- -MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz -+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a -87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 -YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi -7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD -ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 -7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 -nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e -8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx -iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K -zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI= +MIIBiAKCAYEAtQlUSOKGjpdXJ154qmMEa1pEs+9CdSxWiZFkiXBJb0lTafOh8cfF +2IkcWSwzxWwjW4Ad26UQQFh1poGf2QBzVk2vuKCekYzPAs/WqH8VwiXBiWR5R9lh +v/+CkEBYuQOzAhXLN6ZGdPPa2sjdI49rlaIqyLJE4D0TI/VHYmC/vEwqkJUgaGrS +19LhHZimnmouvrnyBPyf00czXlMow0RnmYeHVZ7W5hu7t9TH9o3QAN/GKiFfxFj+ +RkdLM7beQdS0He5YeTaElM5l1YT5d5gHFbOzEQyKHd10ux+bgVcgUeVbBnI1SAIC +w53yc1PkDAiRijSP5j5aWq1djtJPheS13o35HyIf0cHzkNYhKfX5JWPj/cbgdM+C +FL1bnRc8sL5oxmkDoGJhiNZIf4n2WtS8Zu28gUgat6S+vCm/4yavIc/T1g6UiNKE +X41HPbsma/QWUwOL6S+b2qr+7rKqjI5TzVek8vBMellEV4mBvfQU3NDSQ4WvxbTq +ZEOgLPA178nrAgEC -----END DH PARAMETERS----- diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index 311b2821..9ce12980 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -117,7 +117,7 @@ http { include /overrides/*.conf; # Actual logic - {% if WEB_WEBMAIL != '/' %} + {% if WEB_WEBMAIL != '/' and WEBROOT_REDIRECT != 'none' %} location / { {% if WEBROOT_REDIRECT %} try_files $uri {{ WEBROOT_REDIRECT }}; @@ -250,6 +250,7 @@ mail { listen 10025; protocol smtp; smtp_auth plain; + auth_http_header Auth-Port 10025; } # Default IMAP server for the webmail (no encryption, but authentication) @@ -257,6 +258,7 @@ mail { listen 10143; protocol imap; smtp_auth plain; + auth_http_header Auth-Port 10043; } # SMTP is always enabled, to avoid losing emails when TLS is failing @@ -271,6 +273,7 @@ mail { {% endif %} protocol smtp; smtp_auth none; + auth_http_header Auth-Port 25; } # All other protocols are disabled if TLS is failing @@ -283,6 +286,7 @@ mail { {% endif %} protocol imap; imap_auth plain; + auth_http_header Auth-Port 143; } server { @@ -293,6 +297,7 @@ mail { {% endif %} protocol pop3; pop3_auth plain; + auth_http_header Auth-Port 110; } server { @@ -303,6 +308,7 @@ mail { {% endif %} protocol smtp; smtp_auth plain login; + auth_http_header Auth-Port 587; } {% if TLS %} @@ -311,6 +317,7 @@ mail { listen [::]:465 ssl; protocol smtp; smtp_auth plain login; + auth_http_header Auth-Port 465; } server { @@ -318,6 +325,7 @@ mail { listen [::]:993 ssl; protocol imap; imap_auth plain; + auth_http_header Auth-Port 993; } server { @@ -325,6 +333,7 @@ mail { listen [::]:995 ssl; protocol pop3; pop3_auth plain; + auth_http_header Auth-Port 995; } {% endif %} {% endif %} diff --git a/core/nginx/conf/proxy.conf b/core/nginx/conf/proxy.conf index 5a182ea8..e4ff6c93 100644 --- a/core/nginx/conf/proxy.conf +++ b/core/nginx/conf/proxy.conf @@ -1,6 +1,12 @@ # Default proxy setup proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header True-Client-IP $remote_addr; +proxy_set_header Forwarded ""; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; +{% if REAL_IP_HEADER and REAL_IP_FROM %} +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +{% else %} +proxy_set_header X-Forwarded-For $remote_addr; +{% endif %} proxy_http_version 1.1; diff --git a/core/nginx/conf/tls.conf b/core/nginx/conf/tls.conf index 5d7ec031..f663bfd2 100644 --- a/core/nginx/conf/tls.conf +++ b/core/nginx/conf/tls.conf @@ -1,5 +1,10 @@ ssl_certificate {{ TLS[0] }}; ssl_certificate_key {{ TLS[1] }}; +{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %} +ssl_certificate {{ TLS[2] }}; +ssl_certificate_key {{ TLS[3] }}; +ssl_trusted_certificate /etc/ssl/certs/ca-cert-DST_Root_CA_X3.pem; +{% endif %} ssl_session_timeout 1d; ssl_session_tickets off; ssl_dhparam /conf/dhparam.pem; diff --git a/core/nginx/config.py b/core/nginx/config.py index 6fc9c082..9fa75877 100755 --- a/core/nginx/config.py +++ b/core/nginx/config.py @@ -26,11 +26,11 @@ cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem") keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem") args["TLS"] = { "cert": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name), - "letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem", - "/certs/letsencrypt/live/mailu/privkey.pem"), + "letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem", + "/certs/letsencrypt/live/mailu/privkey.pem", "/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem", "/certs/letsencrypt/live/mailu-ecdsa/privkey.pem"), "mail": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name), - "mail-letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem", - "/certs/letsencrypt/live/mailu/privkey.pem"), + "mail-letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem", + "/certs/letsencrypt/live/mailu/privkey.pem", "/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem", "/certs/letsencrypt/live/mailu-ecdsa/privkey.pem"), "notls": None }[args["TLS_FLAVOR"]] diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py index 3fe8ea92..e3db05f1 100755 --- a/core/nginx/letsencrypt.py +++ b/core/nginx/letsencrypt.py @@ -4,7 +4,6 @@ import os import time import subprocess - command = [ "certbot", "-n", "--agree-tos", # non-interactive @@ -14,16 +13,47 @@ command = [ "--cert-name", "mailu", "--preferred-challenges", "http", "--http-01-port", "8008", "--keep-until-expiring", - "--rsa-key-size", "4096", + "--renew-with-new-domains", + "--config-dir", "/certs/letsencrypt", + "--post-hook", "/config.py" +] +command2 = [ + "certbot", + "-n", "--agree-tos", # non-interactive + "-d", os.environ["HOSTNAMES"], + "-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]), + "certonly", "--standalone", + "--cert-name", "mailu-ecdsa", + "--preferred-challenges", "http", "--http-01-port", "8008", + "--keep-until-expiring", + "--key-type", "ecdsa", + "--renew-with-new-domains", "--config-dir", "/certs/letsencrypt", "--post-hook", "/config.py" ] +def format_for_nginx(fullchain, output): + """ We may want to strip ISRG Root X1 out + """ + certs = [] + with open(fullchain, 'r') as pem: + cert = '' + for line in pem: + cert += line + if '-----END CERTIFICATE-----' in line: + certs += [cert] + cert = '' + with open(output, 'w') as pem: + for cert in certs[:-1] if len(certs)>2 and os.getenv('LETSENCRYPT_SHORTCHAIN', default="False") else certs: + pem.write(cert) + # Wait for nginx to start time.sleep(5) -# Run certbot every hour +# Run certbot every day while True: subprocess.call(command) - time.sleep(3600) - + format_for_nginx('/certs/letsencrypt/live/mailu/fullchain.pem', '/certs/letsencrypt/live/mailu/nginx-chain.pem') + subprocess.call(command2) + format_for_nginx('/certs/letsencrypt/live/mailu-ecdsa/fullchain.pem', '/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem') + time.sleep(86400) diff --git a/core/none/Dockerfile b/core/none/Dockerfile index 70041dac..51b8d1c5 100644 --- a/core/none/Dockerfile +++ b/core/none/Dockerfile @@ -1,6 +1,6 @@ # This is an idle image to dynamically replace any component if disabled. -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO CMD sleep 1000000d diff --git a/core/postfix/Dockerfile b/core/postfix/Dockerfile index af29bf91..062155c1 100644 --- a/core/postfix/Dockerfile +++ b/core/postfix/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images RUN apk add --no-cache \ @@ -12,7 +12,7 @@ RUN pip3 install socrate==0.2.0 RUN pip3 install "podop>0.2.5" # Image specific layers under this line -RUN apk add --no-cache postfix postfix-pcre cyrus-sasl-plain cyrus-sasl-login +RUN apk add --no-cache postfix postfix-pcre cyrus-sasl-login COPY conf /conf COPY start.py /start.py diff --git a/core/postfix/conf/main.cf b/core/postfix/conf/main.cf index 8f35f609..99c5179a 100644 --- a/core/postfix/conf/main.cf +++ b/core/postfix/conf/main.cf @@ -32,8 +32,9 @@ mydestination = relayhost = {{ RELAYHOST }} {% if RELAYUSER %} smtp_sasl_auth_enable = yes -smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd -smtp_sasl_security_options = noanonymous +smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd +smtp_sasl_security_options = noanonymous, noplaintext +smtp_sasl_tls_security_options = noanonymous {% endif %} # Recipient delimiter for extended addresses @@ -50,15 +51,20 @@ smtpd_authorized_xclient_hosts={{ POD_ADDRESS_RANGE or SUBNET }} # General TLS configuration tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA tls_preempt_cipherlist = yes -tls_ssl_options = NO_COMPRESSION +tls_ssl_options = NO_COMPRESSION, NO_TICKET # By default, outgoing TLS is more flexible because # 1. not all receiving servers will support TLS, # 2. not all will have and up-to-date TLS stack. -smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('may') }} smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 smtp_tls_protocols =!SSLv2,!SSLv3 -smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache +smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('may') }} +smtp_tls_policy_maps=lmdb:/etc/postfix/tls_policy.map +smtp_tls_CApath = /etc/ssl/certs +smtp_tls_session_cache_database = lmdb:/dev/shm/postfix/smtp_scache +smtpd_tls_session_cache_database = lmdb:/dev/shm/postfix/smtpd_scache +smtp_host_lookup = dns +smtp_dns_support_level = dnssec ############### # Virtual @@ -99,6 +105,8 @@ smtpd_sender_login_maps = ${podop}senderlogin # Restrictions for incoming SMTP, other restrictions are applied in master.cf smtpd_helo_required = yes +check_ratelimit = check_sasl_access ${podop}senderrate + smtpd_client_restrictions = permit_mynetworks, check_sender_access ${podop}senderaccess, diff --git a/core/postfix/conf/master.cf b/core/postfix/conf/master.cf index e45a8ccf..15613476 100644 --- a/core/postfix/conf/master.cf +++ b/core/postfix/conf/master.cf @@ -7,7 +7,8 @@ smtp inet n - n - - smtpd # Internal SMTP service 10025 inet n - n - - smtpd -o smtpd_sasl_auth_enable=yes - -o smtpd_client_restrictions=reject_unlisted_sender,reject_authenticated_sender_login_mismatch,permit + -o smtpd_discard_ehlo_keywords=pipelining + -o smtpd_client_restrictions=$check_ratelimit,reject_unlisted_sender,reject_authenticated_sender_login_mismatch,permit -o smtpd_reject_unlisted_recipient={% if REJECT_UNLISTED_RECIPIENT %}{{ REJECT_UNLISTED_RECIPIENT }}{% else %}no{% endif %} -o cleanup_service_name=outclean outclean unix n - n - 0 cleanup diff --git a/core/postfix/conf/outclean_header_filter.cf b/core/postfix/conf/outclean_header_filter.cf index 03e33ee9..7e0e92d3 100644 --- a/core/postfix/conf/outclean_header_filter.cf +++ b/core/postfix/conf/outclean_header_filter.cf @@ -4,7 +4,7 @@ # Remove the first line of the Received: header. Note that we cannot fully remove the Received: header # because OpenDKIM requires that a header be present when signing outbound mail. The first line is # where the user's home IP address would be. -/^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP])$1 +/^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user ({{OUTCLEAN}} [{{OUTCLEAN_ADDRESS}}])$1 # Remove other typically private information. /^\s*User-Agent:/ IGNORE diff --git a/core/postfix/conf/sasl_passwd b/core/postfix/conf/sasl_passwd index e19d0657..1e32322a 100644 --- a/core/postfix/conf/sasl_passwd +++ b/core/postfix/conf/sasl_passwd @@ -1 +1,2 @@ -{{ RELAYHOST }} {{ RELAYUSER }}:{{ RELAYPASSWORD }} \ No newline at end of file +{{ RELAYHOST }} {{ RELAYUSER }}:{{ RELAYPASSWORD }} + diff --git a/core/postfix/start.py b/core/postfix/start.py index b68303e1..799d42f5 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -8,12 +8,14 @@ import logging as log import sys from podop import run_server +from pwd import getpwnam from socrate import system, conf log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING")) def start_podop(): - os.setuid(100) + os.setuid(getpwnam('postfix').pw_uid) + os.mkdir('/dev/shm/postfix',mode=0o700) url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/" # TODO: Remove verbosity setting from Podop? run_server(0, "postfix", "/tmp/podop.socket", [ @@ -24,7 +26,8 @@ def start_podop(): ("recipientmap", "url", url + "recipient/map/§"), ("sendermap", "url", url + "sender/map/§"), ("senderaccess", "url", url + "sender/access/§"), - ("senderlogin", "url", url + "sender/login/§") + ("senderlogin", "url", url + "sender/login/§"), + ("senderrate", "url", url + "sender/rate/§") ]) def is_valid_postconf_line(line): @@ -36,6 +39,15 @@ os.environ["FRONT_ADDRESS"] = system.get_host_address_from_environment("FRONT", os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin") os.environ["ANTISPAM_MILTER_ADDRESS"] = system.get_host_address_from_environment("ANTISPAM_MILTER", "antispam:11332") os.environ["LMTP_ADDRESS"] = system.get_host_address_from_environment("LMTP", "imap:2525") +os.environ["OUTCLEAN"] = os.environ["HOSTNAMES"].split(",")[0] +try: + _to_lookup = os.environ["OUTCLEAN"] + # Ensure we lookup a FQDN: @see #1884 + if not _to_lookup.endswith('.'): + _to_lookup += '.' + os.environ["OUTCLEAN_ADDRESS"] = system.resolve_hostname(_to_lookup) +except: + os.environ["OUTCLEAN_ADDRESS"] = "10.10.10.10" for postfix_file in glob.glob("/conf/*.cf"): conf.jinja(postfix_file, os.environ, os.path.join("/etc/postfix", os.path.basename(postfix_file))) @@ -56,6 +68,12 @@ for map_file in glob.glob("/overrides/*.map"): os.system("postmap {}".format(destination)) os.remove(destination) +if not os.path.exists("/etc/postfix/tls_policy.map.db"): + with open("/etc/postfix/tls_policy.map", "w") as f: + for domain in ['gmail.com', 'yahoo.com', 'hotmail.com', 'aol.com', 'outlook.com', 'comcast.net', 'icloud.com', 'msn.com', 'hotmail.co.uk', 'live.com', 'yahoo.co.in', 'me.com', 'mail.ru', 'cox.net', 'yahoo.co.uk', 'verizon.net', 'ymail.com', 'hotmail.it', 'kw.com', 'yahoo.com.tw', 'mac.com', 'live.se', 'live.nl', 'yahoo.com.br', 'googlemail.com', 'libero.it', 'web.de', 'allstate.com', 'btinternet.com', 'online.no', 'yahoo.com.au', 'live.dk', 'earthlink.net', 'yahoo.fr', 'yahoo.it', 'gmx.de', 'hotmail.fr', 'shawinc.com', 'yahoo.de', 'moe.edu.sg', 'naver.com', 'bigpond.com', 'statefarm.com', 'remax.net', 'rocketmail.com', 'live.no', 'yahoo.ca', 'bigpond.net.au', 'hotmail.se', 'gmx.at', 'live.co.uk', 'mail.com', 'yahoo.in', 'yandex.ru', 'qq.com', 'charter.net', 'indeedemail.com', 'alice.it', 'hotmail.de', 'bluewin.ch', 'optonline.net', 'wp.pl', 'yahoo.es', 'hotmail.no', 'pindotmedia.com', 'orange.fr', 'live.it', 'yahoo.co.id', 'yahoo.no', 'hotmail.es', 'morganstanley.com', 'wellsfargo.com', 'wanadoo.fr', 'facebook.com', 'yahoo.se', 'fema.dhs.gov', 'rogers.com', 'yahoo.com.hk', 'live.com.au', 'nic.in', 'nab.com.au', 'ubs.com', 'shaw.ca', 'umich.edu', 'westpac.com.au', 'yahoo.com.mx', 'yahoo.com.sg', 'farmersagent.com', 'yahoo.dk', 'dhs.gov']: + f.write(f'{domain}\tsecure\n') + os.system("postmap /etc/postfix/tls_policy.map") + if "RELAYUSER" in os.environ: path = "/etc/postfix/sasl_passwd" conf.jinja("/conf/sasl_passwd", os.environ, path) diff --git a/core/rspamd/Dockerfile b/core/rspamd/Dockerfile index acaf074e..6706ef14 100644 --- a/core/rspamd/Dockerfile +++ b/core/rspamd/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images RUN apk add --no-cache \ diff --git a/docs/Dockerfile b/docs/Dockerfile index 70c9c3c4..289697da 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,20 +1,28 @@ -ARG DISTRO=alpine:3.8 -FROM $DISTRO - -COPY requirements.txt /requirements.txt +# Convert .rst files to .html in temporary build container +FROM python:3.8-alpine3.14 AS build ARG version=master ENV VERSION=$version -RUN apk add --no-cache nginx curl python3 \ - && pip3 install -r /requirements.txt \ - && mkdir /run/nginx - -COPY ./nginx.conf /etc/nginx/conf.d/default.conf +COPY requirements.txt /requirements.txt COPY . /docs -RUN mkdir -p /build/$VERSION \ - && sphinx-build -W /docs /build/$VERSION +RUN apk add --no-cache --virtual .build-deps \ + gcc musl-dev \ + && pip3 install -r /requirements.txt \ + && mkdir -p /build/$VERSION \ + && sphinx-build -W /docs /build/$VERSION \ + && apk del .build-deps + + +# Build nginx deployment image including generated html +FROM nginx:1.21-alpine + +ARG version=master +ENV VERSION=$version + +COPY ./nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /build/$VERSION /build/$VERSION EXPOSE 80/tcp diff --git a/docs/compose/traefik/traefik.toml b/docs/compose/traefik/traefik.toml index c09cf42a..7e09de58 100644 --- a/docs/compose/traefik/traefik.toml +++ b/docs/compose/traefik/traefik.toml @@ -28,6 +28,6 @@ entryPoint = "http" # This should include all of your mail domains, and main= should be your $TRAEFIK_DOMAIN [[acme.domains]] - main = "mail.your.doma.in" - sans = ["web.mail.your.doma.in", "smtp.mail.doma.in", "imap.mail.doma.in"] + main = "mail.example.com" + sans = ["web.mail.example.com", "smtp.mail.example.com", "imap.mail.example.com"] diff --git a/docs/conf.py b/docs/conf.py index 6b19f967..db7008b3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,7 +4,7 @@ import os -extensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode'] +extensions = ['sphinx.ext.imgmath', 'sphinx.ext.viewcode', 'sphinx_rtd_theme'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' @@ -36,7 +36,7 @@ html_context = { 'github_user': 'mailu', 'github_repo': 'mailu', 'github_version': version, - 'stable_version': '1.7', + 'stable_version': '1.8', 'versions': [ ('1.5', '/1.5/'), ('1.6', '/1.6/'), diff --git a/docs/configuration.rst b/docs/configuration.rst index 16ea23c3..3d536fd4 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -37,11 +37,13 @@ The ``POSTMASTER`` is the local part of the postmaster email address. It is recommended to setup a generic value and later configure a mail alias for that address. +The ``WILDCARD_SENDERS`` setting is a comma delimited list of user email addresses that are allowed to send emails from any existing address (spoofing the sender). + The ``AUTH_RATELIMIT`` holds a security setting for fighting attackers that try to guess user passwords. The value is the limit of failed authentication attempts that a single IP address can perform against IMAP, POP and SMTP authentication endpoints. -If ``AUTH_RATELIMIT_SUBNET`` is ``True`` (which is the default), the ``AUTH_RATELIMIT`` +If ``AUTH_RATELIMIT_SUBNET`` is ``True`` (default: False), the ``AUTH_RATELIMIT`` rules does also apply to auth requests coming from ``SUBNET``, especially for the webmail. If you disable this, ensure that the rate limit on the webmail is enforced in a different way (e.g. roundcube plug-in), otherwise an attacker can simply bypass the limit using webmail. @@ -70,8 +72,8 @@ mail in following format: ``[HOST]:PORT``. ``RELAYUSER`` and ``RELAYPASSWORD`` can be used when authentication is needed. By default postfix uses "opportunistic TLS" for outbound mail. This can be changed -by setting ``OUTBOUND_TLS_LEVEL`` to ``encrypt``. This setting is highly recommended -if you are a relayhost that supports TLS. +by setting ``OUTBOUND_TLS_LEVEL`` to ``encrypt`` or ``secure``. This setting is highly recommended +if you are using a relayhost that supports TLS. Similarily by default nginx uses "opportunistic TLS" for inbound mail. This can be changed by setting ``INBOUND_TLS_ENFORCE`` to ``True``. Please note that this is forbidden for @@ -99,14 +101,19 @@ the localpart for DMARC rua and ruf email addresses. Full-text search is enabled for IMAP is enabled by default. This feature can be disabled (e.g. for performance reasons) by setting the optional variable ``FULL_TEXT_SEARCH`` to ``off``. +.. _web_settings: + 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. +- ``WEB_ADMIN`` contains the path to the main admin interface + +- ``WEB_WEBMAIL`` contains the path to the Web email client. + +- ``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. + Alternatively, ``WEBROOT_REDIRECT`` can be set to ``none`` if you are using an Nginx override for ``location /``. + All three options need a leading slash (``/``) to work. .. note:: ``WEBROOT_REDIRECT`` has to point to a valid path on the webserver. @@ -158,6 +165,12 @@ See the `python docs`_ for more information. .. _`python docs`: https://docs.python.org/3.6/library/logging.html#logging-levels +The ``LETSENCRYPT_SHORTCHAIN`` (default: False) setting controls whether we send the ISRG Root X1 certificate in TLS handshakes. This is required for `android handsets older than 7.1.1` but slows down the performance of modern devices. + +.. _`android handsets older than 7.1.1`: https://community.letsencrypt.org/t/production-chain-changes/150739 + +The ``REAL_IP_HEADER`` (default: unset) and ``REAL_IP_FROM`` (default: unset) settings controls whether HTTP headers such as ``X-Forwarded-For`` or ``X-Real-IP`` should be trusted. The former should be the name of the HTTP header to extract the client IP address from and the later a comma separated list of IP addresses designing which proxies to trust. If you are using Mailu behind a reverse proxy, you should set both. Setting the former without the later introduces a security vulnerability allowing a potential attacker to spoof his source address. + Antivirus settings ------------------ diff --git a/docs/faq.rst b/docs/faq.rst index 403fd163..a2c6bd33 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -267,6 +267,8 @@ correct syntax. The following file names will be taken as override configuration - `Nginx`_ - All ``*.conf`` files in the ``nginx`` sub-directory; - `Rspamd`_ - All files in the ``rspamd`` sub-directory. +To override the root location (``/``) in Nginx ``WEBROOT_REDIRECT`` needs to be set to ``none`` in the env file (see :ref:`web settings `). + *Issue reference:* `206`_, `1368`_. I want to integrate Nextcloud 15 (and newer) with Mailu @@ -497,6 +499,8 @@ follow these steps: logging: driver: journald + options: + tag: mailu-front 2. Add the /etc/fail2ban/filter.d/bad-auth.conf @@ -506,6 +510,7 @@ follow these steps: [Definition] failregex = .* client login failed: .+ client:\ ignoreregex = + journalmatch = CONTAINER_TAG=mailu-front 3. Add the /etc/fail2ban/jail.d/bad-auth.conf @@ -513,8 +518,8 @@ follow these steps: [bad-auth] enabled = true + backend = systemd filter = bad-auth - logpath = /var/log/messages bantime = 604800 findtime = 300 maxretry = 10 diff --git a/docs/kubernetes/mailu/admin.yaml b/docs/kubernetes/mailu/admin.yaml deleted file mode 100644 index 08c06e44..00000000 --- a/docs/kubernetes/mailu/admin.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-admin - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-admin - role: mail - tier: backend - spec: - containers: - - name: admin - image: mailu/admin:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - volumeMounts: - - name: maildata - mountPath: /data - subPath: maildata - - name: maildata - mountPath: /dkim - subPath: dkim - ports: - - name: http - containerPort: 80 - protocol: TCP - resources: - requests: - memory: 500Mi - cpu: 500m - limits: - memory: 500Mi - cpu: 500m - volumes: - - name: maildata - persistentVolumeClaim: - claimName: mail-storage ---- - -apiVersion: v1 -kind: Service -metadata: - name: admin - namespace: mailu-mailserver - labels: - app: mailu-admin - role: mail - tier: backend -spec: - selector: - app: mailu-admin - role: mail - tier: backend - ports: - - name: http - port: 80 - protocol: TCP diff --git a/docs/kubernetes/mailu/configmap.yaml b/docs/kubernetes/mailu/configmap.yaml deleted file mode 100644 index 6a674c53..00000000 --- a/docs/kubernetes/mailu/configmap.yaml +++ /dev/null @@ -1,175 +0,0 @@ - apiVersion: v1 - kind: ConfigMap - metadata: - name: mailu-config - namespace: mailu-mailserver - data: - # Mailu main configuration file - # - # Most configuration variables can be modified through the Web interface, - # these few settings must however be configured before starting the mail - # server and require a restart upon change. - - ################################### - # Common configuration variables - ################################### - - # Set this to the path where Mailu data and configuration is stored - ROOT: "/mailu" - - # Mailu version to run (1.0, 1.1, etc. or master) - VERSION: "master" - - # Set to a randomly generated 16 bytes string - SECRET_KEY: "MySup3rS3cr3tPas" - - # Address where listening ports should bind - BIND_ADDRESS4: "127.0.0.1" - #BIND_ADDRESS6: "::1" - - # Main mail domain - DOMAIN: "example.com" - - # Hostnames for this server, separated with comas - HOSTNAMES: "mail.example.com" - - # Postmaster local part (will append the main mail domain) - POSTMASTER: "admin" - - # Choose how secure connections will behave (value: letsencrypt, cert, notls, mail, mail-letsencrypt) - TLS_FLAVOR: "cert" - - # Authentication rate limit (per source IP address) - AUTH_RATELIMIT: "10/minute;1000/hour" - - # Opt-out of statistics, replace with "True" to opt out - DISABLE_STATISTICS: "False" - - ################################### - # Kubernetes configuration - ################################### - - # Use Kubernetes Ingress Controller to handle all actions on port 80 and 443 - # This way we can make use of the advantages of the cert-manager deployment - KUBERNETES_INGRESS: "true" - - # POD_ADDRESS_RANGE is normally provided by default with Kubernetes - # Only use this value when you are using Flannel, Calico or a special kind of CNI - # Provide the IPs of your network interface or bridge which is used for VXLAN network traffic - # POD_ADDRESS_RANGE: 10.2.0.0/16,10.1.6.0/24 - - ################################### - # Optional features - ################################### - - # Expose the admin interface (value: true, false) - ADMIN: "true" - # Run the admin interface in debug mode - #DEBUG: "True" - - # Choose which webmail to run if any (values: roundcube, rainloop, none) - WEBMAIL: "roundcube" - - # Dav server implementation (value: radicale, none) - WEBDAV: "radicale" - - # Antivirus solution (value: clamav, none) - ANTIVIRUS: "clamav" - - ################################### - # Mail settings - ################################### - - # Message size limit in bytes - # Default: accept messages up to 50MB - MESSAGE_SIZE_LIMIT: "50000000" - - # Will relay all outgoing mails if configured - #RELAYHOST= - - # This part is needed for the XCLIENT login for postfix. This should be the POD ADDRESS range - FRONT_ADDRESS: "front.mailu-mailserver.svc.cluster.local" - - # This value is needed by the webmail to find the correct imap backend - IMAP_ADDRESS: "imap.mailu-mailserver.svc.cluster.local" - - # This value is used by Dovecot to find the Redis server in the cluster - REDIS_ADDRESS: "redis.mailu-mailserver.svc.cluster.local" - - # Fetchmail delay - FETCHMAIL_DELAY: "600" - - # Recipient delimiter, character used to delimiter localpart from custom address part - # e.g. localpart+custom@domain;tld - RECIPIENT_DELIMITER: "+" - - # DMARC rua and ruf email - DMARC_RUA: "root" - DMARC_RUF: "root" - - # Welcome email, enable and set a topic and body if you wish to send welcome - # emails to all users. - WELCOME: "false" - WELCOME_SUBJECT: "Welcome to your new email account" - WELCOME_BODY: "Welcome to your new email account, if you can read this, then it is configured properly!" - - ################################### - # Web settings - ################################### - - # Path to the admin interface if enabled - # Kubernetes addition: You need to change ALL the ingresses, when you want this URL to be different!!! - WEB_ADMIN: "/admin" - - # Path to the webmail if enabled - # Currently, this is not used, because we intended to use a different subdomain: webmail.example.com - # This option can be added in a feature release - WEB_WEBMAIL: "/webmail" - - # Website name - SITENAME: "Mailu" - - # Linked Website URL - WEBSITE: "https://example.com" - - # Registration reCaptcha settings (warning, this has some privacy impact) - # RECAPTCHA_PUBLIC_KEY= - # RECAPTCHA_PRIVATE_KEY= - - # Domain registration, uncomment to enable - # DOMAIN_REGISTRATION=true - - ################################### - # Advanced settings - ################################### - - # Create an admin account if it does not exist yet. It will also create the email domain for the account. - # INITIAL_ADMIN_ACCOUNT: "admin" - # INITIAL_ADMIN_DOMAIN: "example.com" - # INITIAL_ADMIN_PW: "s3cr3t" - - # Docker-compose project name, this will prepended to containers names. - COMPOSE_PROJECT_NAME: "mailu" - - # Default password scheme used for newly created accounts and changed passwords - # (value: SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT) - PASSWORD_SCHEME: "SHA512-CRYPT" - - # Header to take the real ip from - #REAL_IP_HEADER: - - # IPs for nginx set_real_ip_from (CIDR list separated by commas) - #REAL_IP_FROM: - - # Host settings - HOST_IMAP: "imap.mailu-mailserver.svc.cluster.local" - HOST_POP3: "imap.mailu-mailserver.svc.cluster.local" - HOST_SMTP: "smtp.mailu-mailserver.svc.cluster.local" - HOST_AUTHSMTP: "smtp.mailu-mailserver.svc.cluster.local" - HOST_WEBMAIL: "webmail.mailu-mailserver.svc.cluster.local" - HOST_ADMIN: "admin.mailu-mailserver.svc.cluster.local" - HOST_WEBDAV: "webdav.mailu-mailserver.svc.cluster.local:5232" - HOST_ANTISPAM_MILTER: "antispam.mailu-mailserver.svc.cluster.local:11332" - HOST_ANTISPAM_WEBUI: "antispam.mailu-mailserver.svc.cluster.local:11334" - HOST_ANTIVIRUS: "antivirus.mailu-mailserver.svc.cluster.local:3310" - HOST_REDIS: "redis.mailu-mailserver.svc.cluster.local" diff --git a/docs/kubernetes/mailu/fetchmail.yaml b/docs/kubernetes/mailu/fetchmail.yaml deleted file mode 100644 index d454f95b..00000000 --- a/docs/kubernetes/mailu/fetchmail.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-fetchmail - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-fetchmail - role: mail - tier: backend - spec: - containers: - - name: fetchmail - image: mailu/fetchmail:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - volumeMounts: - - name: maildata - mountPath: /data - subPath: maildata - ports: - - containerPort: 5232 - - containerPort: 80 - resources: - requests: - memory: 100Mi - cpu: 100m - limits: - memory: 100Mi - cpu: 100m - volumes: - - name: maildata - persistentVolumeClaim: - claimName: mail-storage diff --git a/docs/kubernetes/mailu/front.yaml b/docs/kubernetes/mailu/front.yaml deleted file mode 100644 index 2fba1026..00000000 --- a/docs/kubernetes/mailu/front.yaml +++ /dev/null @@ -1,148 +0,0 @@ -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: mailu-front - namespace: mailu-mailserver - labels: - k8s-app: mail-loadbalancer - component: ingress-controller - type: nginx -spec: - selector: - matchLabels: - k8s-app: mail-loadbalancer - component: ingress-controller - type: nginx - template: - metadata: - labels: - k8s-app: mail-loadbalancer - component: ingress-controller - type: nginx - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-role.kubernetes.io/node - operator: Exists - nodeSelector: - node-role.kubernetes.io/node: "" - dnsPolicy: ClusterFirstWithHostNet - restartPolicy: Always - terminationGracePeriodSeconds: 60 - containers: - - name: front - image: mailu/nginx:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - volumeMounts: - - name: certs - mountPath: /certs - ports: - - name: pop3 - containerPort: 110 - hostPort: 110 - protocol: TCP - - name: pop3s - containerPort: 995 - hostPort: 995 - protocol: TCP - - name: imap - containerPort: 143 - hostPort: 143 - protocol: TCP - - name: imaps - containerPort: 993 - hostPort: 993 - protocol: TCP - - name: smtp - containerPort: 25 - hostPort: 25 - protocol: TCP - - name: smtps - containerPort: 465 - hostPort: 465 - protocol: TCP - - name: smtpd - containerPort: 587 - hostPort: 587 - protocol: TCP - # internal services (not exposed externally) - - name: smtp-auth - containerPort: 10025 - protocol: TCP - - name: imap-auth - containerPort: 10143 - protocol: TCP - - name: auth - containerPort: 8000 - protocol: TCP - - name: http - containerPort: 80 - protocol: TCP - resources: - requests: - memory: 100Mi - cpu: 100m - limits: - memory: 200Mi - cpu: 200m - volumes: - - name: certs - secret: - items: - - key: tls.crt - path: cert.pem - - key: tls.key - path: key.pem - secretName: letsencrypt-certs-all ---- -apiVersion: v1 -kind: Service -metadata: - name: front - namespace: mailu-mailserver - labels: - k8s-app: mail-loadbalancer - component: ingress-controller - type: nginx -spec: - selector: - k8s-app: mail-loadbalancer - component: ingress-controller - type: nginx - ports: - - name: pop3 - port: 110 - protocol: TCP - - name: pop3s - port: 995 - protocol: TCP - - name: imap - port: 143 - protocol: TCP - - name: imaps - port: 993 - protocol: TCP - - name: smtp - port: 25 - protocol: TCP - - name: smtps - port: 465 - protocol: TCP - - name: smtpd - port: 587 - protocol: TCP - - name: smtp-auth - port: 10025 - protocol: TCP - - name: imap-auth - port: 10143 - protocol: TCP - - name: http - port: 80 - protocol: TCP diff --git a/docs/kubernetes/mailu/imap.yaml b/docs/kubernetes/mailu/imap.yaml deleted file mode 100644 index 64e69930..00000000 --- a/docs/kubernetes/mailu/imap.yaml +++ /dev/null @@ -1,84 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-imap - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-imap - role: mail - tier: backend - spec: - containers: - - name: imap - image: mailu/dovecot:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - volumeMounts: - - mountPath: /data - name: maildata - subPath: maildata - - mountPath: /mail - name: maildata - subPath: mailstate - - mountPath: /overrides - name: maildata - subPath: overrides - ports: - - name: imap-auth - containerPort: 2102 - - name: imap-transport - containerPort: 2525 - - name: pop3 - containerPort: 110 - - name: imap-default - containerPort: 143 - - name: sieve - containerPort: 4190 - resources: - requests: - memory: 1Gi - cpu: 1000m - limits: - memory: 1Gi - cpu: 1000m - volumes: - - name: maildata - persistentVolumeClaim: - claimName: mail-storage ---- -apiVersion: v1 -kind: Service -metadata: - name: imap - namespace: mailu-mailserver - labels: - app: mailu - role: mail - tier: backend -spec: - selector: - app: mailu-imap - role: mail - tier: backend - ports: - - name: imap-auth - port: 2102 - protocol: TCP - - name: imap-transport - port: 2525 - protocol: TCP - - name: pop3 - port: 110 - protocol: TCP - - name: imap-default - port: 143 - protocol: TCP - - name: sieve - port: 4190 - protocol: TCP diff --git a/docs/kubernetes/mailu/index.rst b/docs/kubernetes/mailu/index.rst index 0af3942e..853f3689 100644 --- a/docs/kubernetes/mailu/index.rst +++ b/docs/kubernetes/mailu/index.rst @@ -3,222 +3,9 @@ Kubernetes setup ================ -> Hold up! -> These instructions are not recommended for setting up Mailu in a production Kubernetes environment. -> Please see [the Helm Chart documentation](https://github.com/Mailu/helm-charts/blob/master/mailu/README.md). +Please see `the Helm Chart documentation`_. -Prequisites ------------ +We are looking for maintainers: if you are interested please join our `Matrix`_ room. -Structure -~~~~~~~~~ - -There’s chosen to have a double NGINX stack for Mailu, this way the main -ingress can still be used to access other websites/domains on your -cluster. This is the current structure: - -- ``NGINX Ingress controller``: Listens to the nodes ports 80 & 443. We have chosen to have a double NGINX stack for Mailu. -- ``Cert manager``: Creates automatic Lets Encrypt certificates based on an ``Ingress``-objects domain name. -- ``Mailu NGINX Front daemonset``: This daemonset runs in parallel with the Nginx Ingress Controller and only listens on all E-mail specific ports (25, 110, 143, 587,...). It also listens on 80 and delegates the various http endpoints to the correct services. -- ``Mailu components``: All Mailu components (imap, smtp, security, webmail,...) are split into separate files to make them more handy to use, you can find the ``YAML`` files in this directory - -What you need -~~~~~~~~~~~~~ - -- A working Kubernetes cluster (tested with 1.10.5) -- A working `cert-manager`_ installation -- A working nginx-ingress controller needed for the lets-encrypt - certificates. You can find those files in the ``nginx`` subfolder. - Other ingress controllers that support cert-manager (e.g. traefik) - should also work. - -Cert manager -^^^^^^^^^^^^ - -The ``Cert-manager`` is quite easy to deploy using Helm when reading the -`docs`_. After booting the ``Cert-manager`` you’ll need a -``ClusterIssuer`` which takes care of all required certificates through -``Ingress`` items. We chose to provide a ``clusterIssuer`` so you can provide SSL certificates -for other namespaces (different websites/services), if you don't need this option, you can easily change this by -changing ``clusterIssuer`` to ``Issuer`` and adding the ``namespace: mailu-mailserver`` to the metadata. -An example of a production and a staging ``clusterIssuer``: - -.. code:: yaml - - # This clusterIssuer example uses the staging environment for testing first - apiVersion: certmanager.k8s.io/v1alpha1 - kind: ClusterIssuer - metadata: - name: letsencrypt-stage - spec: - acme: - email: something@example.com - http01: {} - privateKeySecretRef: - name: letsencrypt-stage - server: https://acme-staging-v02.api.letsencrypt.org/directory - -.. code:: yaml - - # This clusterIssuer example uses the production environment - apiVersion: certmanager.k8s.io/v1alpha1 - kind: ClusterIssuer - metadata: - name: letsencrypt-prod - spec: - acme: - email: something@example.com - http01: {} - privateKeySecretRef: - name: letsencrypt-prod - server: https://acme-v02.api.letsencrypt.org/directory - -**IMPORTANT**: ``ingress.yaml`` uses the ``letsencrypt-stage`` ``clusterIssuer``. If you are ready for production, -change this field in ``ingress.yaml`` file to ``letsencrypt-prod`` or whatever name you chose for the production. -If you choose for ``Issuer`` instead of ``clusterIssuer`` you also need to change the annotation to ``certmanager.k8s.io/issuer`` instead of ``certmanager.k8s.io/cluster-issuer`` - -Deploying Mailu ---------------- - -All manifests can be found in the ``mailu`` subdirectory. All commands -below need to be run from this subdirectory - -Personalization -~~~~~~~~~~~~~~~ - -- All services run in the same namespace, currently ``mailu-mailserver``. So if you want to use a different one, change the ``namespace`` value in **every** file -- Check the ``storage-class`` field in the ``pvc.yaml`` file, you can also change the sizes to your liking. Note that you need ``RWX`` (read-write-many) and ``RWO`` (read-write-once) storageclasses. -- Check the ``configmap.yaml`` and adapt it to your needs. Be sure to check the kubernetes DNS values at the end (if you use a different namespace) -- Check the ``ingress.yaml`` file and change it to the domain you want (this is for the kubernetes ingress controller to handle the admin, webmail, webdav and auth connections) - -Installation ------------- - -Boot the Mailu components -~~~~~~~~~~~~~~~~~~~~~~~~~ - -To start Mailu, run the following commands from the ``docs/kubernetes/mailu`` directory - -.. code-block:: bash - - kubectl create -f rbac.yaml - kubectl create -f configmap.yaml - kubectl create -f pvc.yaml - kubectl create -f redis.yaml - kubectl create -f front.yaml - kubectl create -f webmail.yaml - kubectl create -f imap.yaml - kubectl create -f security.yaml - kubectl create -f smtp.yaml - kubectl create -f fetchmail.yaml - kubectl create -f admin.yaml - kubectl create -f webdav.yaml - kubectl create -f ingress.yaml - - -Create the first admin account -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When the cluster is online you need to create you master user to access https://mail.example.com/admin - -You can create it now manually, or have the system create it automatically. - -If you want the system to create the admin user account automatically, see :ref:`admin_account` -about the environment variables needed (``INITIAL_ADMIN_*``). -Also, important, taking into consideration that a pod in Kubernetes can be stopped/rescheduled at -any time, you should set ``INITIAL_ADMIN_MODE`` to either ``update`` or ``ifmissing`` - depending on what you -want to happen to its password. - - -To create the admin user account manually, enter the main ``admin`` pod: - -.. code-block:: bash - - kubectl -n mailu-mailserver get po - kubectl -n mailu-mailserver exec -it mailu-admin-.... /bin/sh - -And in the pod run the following command. The command uses following entries: - -.. code-block:: bash - - flask mailu admin root example.com password - -- ``admin`` Make it an admin user -- ``root`` The first part of the e-mail address (ROOT@example.com) -- ``example.com`` the domain appendix -- ``password`` the chosen password for the user - - -Now you should be able to login on the mail account: https://mail.example.com/admin - - -Adaptations ------------ - -Dovecot -~~~~~~~ - -- If you are using Dovecot on a shared file system (Glusterfs, NFS,...), you need to create a special override otherwise a lot of indexing errors will occur on your Dovecot pod. -- I also higher the number of max connections per IP. Now it's limited to 10. - -Enter the dovecot pod: - -.. code:: bash - - kubectl -n mailu-mailserver get po - kubectl -n mailu-mailserver exec -it mailu-imap-.... /bin/sh - -Create the file ``overrides/dovecot.conf`` - -.. code:: bash - - vi /overrides/dovecot.conf - -And enter following contents: - -.. code:: bash - - mail_nfs_index = yes - mail_nfs_storage = yes - mail_fsync = always - mmap_disable = yes - mail_max_userip_connections=100 - -Save and close the file and delete the imap pod to get it recreated. - -.. code:: bash - - kubectl -n mailu-mailserver delete po/mailu-imap-.... - -Wait for the pod to recreate and you're online! -Happy mailing! - -.. _here: https://github.com/hacor/Mailu/blob/master/core/postfix/conf/main.cf#L35 -.. _cert-manager: https://github.com/jetstack/cert-manager -.. _docs: https://cert-manager.io/docs/installation/kubernetes/#installing-with-helm - -Imap login fix -~~~~~~~~~~~~~~ - -If it seems you're not able to login using IMAP on your Mailu accounts, check the logs of the imap container to see whether it's a permissions problem on the database. -This problem can be easily fixed by running following commands: - -.. code:: bash - - kubectl -n mailu-mailserver exec -it mailu-imap-... /bin/sh - chmod 777 /data/main.db - -If the login problem still persists, or more specific, happens now and then and you see some Auth problems on your webmail or mail client, try following steps: - -- Add ``auth_debug=yes`` to the ``/overrides/dovecot.conf`` file and delete the pod in order to start a new one, which loads the configuration -- Depending on your network configuration you could still see some ``allow_nets check failed`` results in the logs. This means that the IP is not allowed a login -- If this is happening your network plugin has troubles with the Nginx Ingress Controller using the ``hostNetwork: true`` option. Known cases: Flannel and Calico. -- You should uncomment ``POD_ADDRESS_RANGE`` in the ``configmap.yaml`` file and add the IP range of your pod network bridge (the range that sadly has failed the ``allowed_nets`` test) -- Delete the Admin pod and wait for it to restart - -.. code:: bash - - kubectl -n mailu-mailserver get po - kubectl -n mailu-mailserver delete po/mailu-admin... - -Happy mailing! +.. _`the Helm Chart documentation`: https://github.com/Mailu/helm-charts/blob/master/mailu/README.md +.. _`Matrix`: https://matrix.to/#/#mailu:tedomum.net diff --git a/docs/kubernetes/mailu/ingress.yaml b/docs/kubernetes/mailu/ingress.yaml deleted file mode 100644 index 5a941e97..00000000 --- a/docs/kubernetes/mailu/ingress.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: apps/v1 -kind: Ingress -metadata: - name: mailu-ingress - namespace: mailu-mailserver - annotations: - kubernetes.io/tls-acme: "true" - certmanager.k8s.io/cluster-issuer: letsencrypt-stage - labels: - app: mailu - role: mail - tier: backend -spec: - tls: - - hosts: - - "mail.example.com" - secretName: letsencrypt-certs-all # If unsure how to generate these, check out https://github.com/ployst/docker-letsencrypt - rules: - - host: "mail.example.com" - http: - paths: - - path: "/" - backend: - serviceName: front - servicePort: 80 diff --git a/docs/kubernetes/mailu/pvc.yaml b/docs/kubernetes/mailu/pvc.yaml deleted file mode 100644 index 0ec2852f..00000000 --- a/docs/kubernetes/mailu/pvc.yaml +++ /dev/null @@ -1,27 +0,0 @@ -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: redis-hdd - namespace: mailu-mailserver - annotations: - volume.beta.kubernetes.io/storage-class: "glusterblock-hdd" -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: mail-storage - namespace: mailu-mailserver - annotations: - volume.beta.kubernetes.io/storage-class: "gluster-heketi-hdd" -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 100Gi diff --git a/docs/kubernetes/mailu/rbac.yaml b/docs/kubernetes/mailu/rbac.yaml deleted file mode 100644 index 33255130..00000000 --- a/docs/kubernetes/mailu/rbac.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: mailu-mailserver \ No newline at end of file diff --git a/docs/kubernetes/mailu/redis.yaml b/docs/kubernetes/mailu/redis.yaml deleted file mode 100644 index f453a3ff..00000000 --- a/docs/kubernetes/mailu/redis.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-redis - namespace: mailu-mailserver -spec: - replicas: 1 - selector: - matchLabels: - app: mailu-redis - role: mail - tier: backend - template: - metadata: - labels: - app: mailu-redis - role: mail - tier: backend - spec: - containers: - - name: redis - image: redis:5-alpine - imagePullPolicy: Always - volumeMounts: - - mountPath: /data - name: redisdata - ports: - - containerPort: 6379 - name: redis - protocol: TCP - resources: - requests: - memory: 200Mi - cpu: 100m - limits: - memory: 300Mi - cpu: 200m - volumes: - - name: redisdata - persistentVolumeClaim: - claimName: redis-hdd ---- -apiVersion: v1 -kind: Service -metadata: - name: redis - namespace: mailu-mailserver - labels: - app: mailu-redis - role: mail - tier: backend -spec: - selector: - app: mailu-redis - role: mail - tier: backend - ports: - - name: redis - port: 6379 - protocol: TCP diff --git a/docs/kubernetes/mailu/security.yaml b/docs/kubernetes/mailu/security.yaml deleted file mode 100644 index 419c7ac4..00000000 --- a/docs/kubernetes/mailu/security.yaml +++ /dev/null @@ -1,115 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-security - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-security - role: mail - tier: backend - spec: - containers: - - name: antispam - image: mailu/rspamd:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - resources: - requests: - memory: 100Mi - cpu: 100m - limits: - memory: 200Mi - cpu: 200m - ports: - - name: antispam - containerPort: 11332 - protocol: TCP - - name: antispam-http - containerPort: 11334 - protocol: TCP - volumeMounts: - - name: filter - subPath: filter - mountPath: /var/lib/rspamd - - name: filter - mountPath: /dkim - subPath: dkim - - name: filter - mountPath: /etc/rspamd/override.d - subPath: rspamd-overrides - - name: antivirus - image: mailu/clamav:master - imagePullPolicy: Always - resources: - requests: - memory: 1Gi - cpu: 1000m - limits: - memory: 2Gi - cpu: 1000m - envFrom: - - configMapRef: - name: mailu-config - ports: - - name: antivirus - containerPort: 3310 - protocol: TCP - volumeMounts: - - name: filter - subPath: filter - mountPath: /data - volumes: - - name: filter - persistentVolumeClaim: - claimName: mail-storage - ---- - -apiVersion: v1 -kind: Service -metadata: - name: antispam - namespace: mailu-mailserver - labels: - app: mailu-antispam - role: mail - tier: backend -spec: - selector: - app: mailu-security - role: mail - tier: backend - ports: - - name: antispam - port: 11332 - protocol: TCP - - name: antispam-http - protocol: TCP - port: 11334 - ---- - -apiVersion: v1 -kind: Service -metadata: - name: antivirus - namespace: mailu-mailserver - labels: - app: mailu-antivirus - role: mail - tier: backend -spec: - selector: - app: mailu-security - role: mail - tier: backend - ports: - - name: antivirus - port: 3310 - protocol: TCP diff --git a/docs/kubernetes/mailu/smtp.yaml b/docs/kubernetes/mailu/smtp.yaml deleted file mode 100644 index 6002d508..00000000 --- a/docs/kubernetes/mailu/smtp.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-smtp - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-smtp - role: mail - tier: backend - spec: - containers: - - name: smtp - image: mailu/postfix:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - resources: - requests: - memory: 2Gi - cpu: 500m - limits: - memory: 2Gi - cpu: 500m - volumeMounts: - - mountPath: /queue - name: maildata - subPath: mailqueue - - mountPath: /overrides - name: maildata - subPath: overrides - ports: - - name: smtp - containerPort: 25 - protocol: TCP - - name: smtp-ssl - containerPort: 465 - protocol: TCP - - name: smtp-starttls - containerPort: 587 - protocol: TCP - - name: smtp-auth - containerPort: 10025 - protocol: TCP - volumes: - - name: maildata - persistentVolumeClaim: - claimName: mail-storage ---- -apiVersion: v1 -kind: Service -metadata: - name: smtp - namespace: mailu-mailserver - labels: - app: mailu - role: mail - tier: backend -spec: - selector: - app: mailu-smtp - role: mail - tier: backend - ports: - - name: smtp - port: 25 - protocol: TCP - - name: smtp-ssl - port: 465 - protocol: TCP - - name: smtp-starttls - port: 587 - protocol: TCP - - name: smtp-auth - port: 10025 - protocol: TCP diff --git a/docs/kubernetes/mailu/webdav.yaml b/docs/kubernetes/mailu/webdav.yaml deleted file mode 100644 index 57dde9a9..00000000 --- a/docs/kubernetes/mailu/webdav.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-webdav - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-webdav - role: mail - tier: backend - spec: - containers: - - name: radicale - image: mailu/radicale:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - volumeMounts: - - mountPath: /data - name: maildata - subPath: dav - ports: - - containerPort: 5232 - - containerPort: 80 - resources: - requests: - memory: 100Mi - cpu: 100m - limits: - memory: 100Mi - cpu: 100m - volumes: - - name: maildata - persistentVolumeClaim: - claimName: mail-storage ---- - -apiVersion: v1 -kind: Service -metadata: - name: webdav - namespace: mailu-mailserver - labels: - app: mailu-webdav - role: mail - tier: backend -spec: - selector: - app: mailu-webdav - role: mail - tier: backend - ports: - ports: - - name: http - port: 80 - protocol: TCP - - name: http-ui - port: 5232 - protocol: TCP diff --git a/docs/kubernetes/mailu/webmail.yaml b/docs/kubernetes/mailu/webmail.yaml deleted file mode 100644 index 679ea84a..00000000 --- a/docs/kubernetes/mailu/webmail.yaml +++ /dev/null @@ -1,57 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailu-roundcube - namespace: mailu-mailserver -spec: - replicas: 1 - template: - metadata: - labels: - app: mailu-roundcube - role: mail - tier: frontend - spec: - containers: - - name: roundcube - image: mailu/roundcube:master - imagePullPolicy: Always - envFrom: - - configMapRef: - name: mailu-config - resources: - requests: - memory: 100Mi - cpu: 100m - limits: - memory: 200Mi - cpu: 200m - volumeMounts: - - mountPath: /data - name: maildata - subPath: webmail - ports: - - containerPort: 80 - volumes: - - name: maildata - persistentVolumeClaim: - claimName: mail-storage ---- -apiVersion: v1 -kind: Service -metadata: - name: webmail - namespace: mailu-mailserver - labels: - app: mailu-roundcube - role: mail - tier: frontend -spec: - selector: - app: mailu-roundcube - role: mail - tier: frontend - ports: - - name: http - port: 80 - protocol: TCP diff --git a/docs/kubernetes/nginx/default-http-backend.yaml b/docs/kubernetes/nginx/default-http-backend.yaml deleted file mode 100644 index cf881c53..00000000 --- a/docs/kubernetes/nginx/default-http-backend.yaml +++ /dev/null @@ -1,55 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: default-http-backend - labels: - app: default-http-backend - namespace: kube-ingress -spec: - replicas: 1 - selector: - matchLabels: - app: default-http-backend - template: - metadata: - labels: - app: default-http-backend - spec: - terminationGracePeriodSeconds: 60 - containers: - - name: default-http-backend - # Any image is permissible as long as: - # 1. It serves a 404 page at / - # 2. It serves 200 on a /healthz endpoint - image: gcr.io/google_containers/defaultbackend:1.4 - livenessProbe: - httpGet: - path: /healthz - port: 8080 - scheme: HTTP - initialDelaySeconds: 30 - timeoutSeconds: 5 - ports: - - containerPort: 8080 - resources: - limits: - cpu: 10m - memory: 20Mi - requests: - cpu: 10m - memory: 20Mi ---- - -apiVersion: v1 -kind: Service -metadata: - name: default-http-backend - namespace: kube-ingress - labels: - app: default-http-backend -spec: - ports: - - port: 80 - targetPort: 8080 - selector: - app: default-http-backend diff --git a/docs/kubernetes/nginx/nginx-ingress.yaml b/docs/kubernetes/nginx/nginx-ingress.yaml deleted file mode 100644 index d8b71e21..00000000 --- a/docs/kubernetes/nginx/nginx-ingress.yaml +++ /dev/null @@ -1,127 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - # keep it under 24 chars - name: ingress-lb - namespace: kube-ingress - labels: - k8s-app: ingress-lb - component: ingress-controller -spec: - type: ClusterIP - selector: - k8s-app: ingress-lb - component: ingress-controller - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 80 - - name: https - protocol: TCP - port: 443 - targetPort: 443 ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: udp-services - namespace: kube-ingress - ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: tcp-services - namespace: kube-ingress -data: - ---- -apiVersion: v1 -data: - enable-vts-status: "true" -kind: ConfigMap -metadata: - name: nginx-ingress-lb-conf - namespace: kube-ingress ---- -apiVersion: apps/v1beta2 -kind: DaemonSet -metadata: - name: ingress-controller - namespace: kube-ingress - annotations: - prometheus.io/port: "10254" - prometheus.io/scrape: "true" - labels: - k8s-app: ingress-lb - component: ingress-controller - type: nginx -spec: - updateStrategy: - rollingUpdate: - maxUnavailable: 1 - type: RollingUpdate - selector: - matchLabels: - k8s-app: ingress-lb - component: ingress-controller - type: nginx - template: - metadata: - labels: - k8s-app: ingress-lb - component: ingress-controller - type: nginx - spec: - serviceAccount: kube-nginx-ingress - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-role.kubernetes.io/master - operator: DoesNotExist - containers: - - name: nginx-ingress-lb - image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.16.2 - args: - - /nginx-ingress-controller - - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - - --annotations-prefix=ingress.kubernetes.io - - --enable-ssl-passthrough - # use downward API - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 - readinessProbe: - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - livenessProbe: - initialDelaySeconds: 10 - timeoutSeconds: 1 - httpGet: - path: /healthz - port: 10254 - scheme: HTTP - hostNetwork: true - nodeSelector: - node-role.kubernetes.io/node: "" - dnsPolicy: ClusterFirstWithHostNet - restartPolicy: Always - terminationGracePeriodSeconds: 60 diff --git a/docs/kubernetes/nginx/rbac.yaml b/docs/kubernetes/nginx/rbac.yaml deleted file mode 100644 index d3c01384..00000000 --- a/docs/kubernetes/nginx/rbac.yaml +++ /dev/null @@ -1,129 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: kube-ingress ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kube-nginx-ingress - namespace: kube-ingress ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRole -metadata: - name: kube-nginx-ingress -rules: - - apiGroups: - - "" - resources: - - configmaps - - endpoints - - nodes - - pods - - secrets - verbs: - - list - - watch - - update - - apiGroups: - - "" - resources: - - nodes - verbs: - - get - - apiGroups: - - "" - resources: - - services - verbs: - - get - - list - - watch - - apiGroups: - - "extensions" - resources: - - ingresses - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - events - verbs: - - create - - patch - - apiGroups: - - "extensions" - resources: - - ingresses/status - verbs: - - update ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: Role -metadata: - name: kube-nginx-ingress - namespace: kube-ingress -rules: - - apiGroups: - - "" - resources: - - configmaps - - pods - - secrets - - namespaces - verbs: - - get - - apiGroups: - - "" - resources: - - configmaps - resourceNames: - - "ingress-controller-leader-nginx" - verbs: - - get - - update - - apiGroups: - - "" - resources: - - configmaps - verbs: - - create - - apiGroups: - - "" - resources: - - endpoints - verbs: - - get - - create - - update ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: RoleBinding -metadata: - name: kube-nginx-ingress - namespace: kube-ingress -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: kube-nginx-ingress -subjects: - - kind: ServiceAccount - name: kube-nginx-ingress - namespace: kube-ingress ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: kube-nginx-ingress -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: kube-nginx-ingress -subjects: - - kind: ServiceAccount - name: kube-nginx-ingress - namespace: kube-ingress \ No newline at end of file diff --git a/docs/releases.rst b/docs/releases.rst index 7a15d1fa..6c672538 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,8 +1,81 @@ Release notes ============= -Mailu 1.8 - 2020-10-02 ----------------------- +Mailu 1.8 - 2021-08-7 +--------------------- + +The full 1.8 release is finally ready. There have been some changes in the contributors team. Many people from the contributors team have stepped back due to changed priorities in their life. +We are very grateful for all their contributions and hope we will see them back again in the future. +This is the main reason why it took so long for 1.8 to be fully released. + +Fortunately more people have decided to join the project. Some very nice contributions have been made which will become part of the next 1.9 release. +We hope that future Mailu releases will be released more quickly now we have more active contributors again. + +For a list of all changes refer to `CHANGELOG.md` in the root folder of the Mailu github project. Please read the 'Override location changes' section further on this page. It contains important information for the people who use the overrides folder. + +New Functionality & Improvements +```````````````````````````````` + +Here’s a short summary of new features: + +- Roundcube and Rainloop have been updated. +- All dependencies have been updated to the latest security update. +- Fail2ban documentation has been improved. +- Switch from client side (cookie) sessions to server side sessions and protect against session-fixation attacks. We recommend that you change your SECRET_KEY after upgrading. +- Full-text-search is back after having been disabled for a while due to nasty bugs. It can still be disabled via the mailu.env file. +- Tons of documentation improvements, especially geared towards new users. +- (Experimental) support for different architectures, such as ARM. +- Improvements around webmails, such as CardDAV, GPG and a new skin for an updated roundcube, and support for MySQL for it. Updated Rainloop, too. +- Improvements around relaying, such as AUTH LOGIN and non-standard port support. +- Update to alpine:3.14 as baseimage for most containers. +- Setup warns users about compose-IPv6 deployments which have caused open relays in the past. +- Improved handling of upper-vs-lowercase aliases and user-addresses. +- Improved rate-limiting system. +- Support for SRS. +- Japanese localisation is now available. + + +Upgrading +````````` + +Upgrade should run fine as long as you generate a new compose or stack +configuration and upgrade your mailu.env. + +Please note that the shipped image for PostgreSQL database is deprecated. +The shipped image for PostgreSQL is not maintained anymore from release 1.8. +We recommend switching to an external PostgreSQL image as soon as possible. + +Override location changes +^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you have regenerated the Docker compose and environment files, there are some changes to the configuration overrides. +Override files are now mounted read-only into the containers. The Dovecot and Postfix overrides are moved in their own sub-directory. If there are local override files, they will need to be moved from ``overrides/`` to ``overrides/dovecot`` and ``overrides/postfix/``. + +Recreate SECRET_KEY after upgrading +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Improvements have been made to protect again session-fixation attacks. +To be fully protected, it is required to change your SECRET_KEY in Mailu.env after upgrading. +A new SECRET_KEY is generated when you recreate your docker-compose.yml & mailu.env file via setup.mailu.io. + +The SECRET_KEY is an uppercase alphanumeric string of length 16. You can manually create such a string via +```cat /dev/urandom | tr -dc 'A-Z0-9' | fold -w ${1:-16} | head -n 1``` + +After changing mailu.env, it is required to recreate all containers for the changes to be propagated. + +Update your DNS SPF Records +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It has become known that the SPF DNS records generated by the admin interface are not completely standard compliant anymore. Please check the DNS records for your domains and compare them to what the new admin-interface instructs you to use. In most cases, this should be a simple copy-paste operation for you …. + +Fixed hostname for antispam service +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For history to be retained in Rspamd, the antispam container requires a static hostname. When you re-generate your docker-compose.yml file (or helm-chart), this will be covered. + + +Mailu 1.8rc - 2020-10-02 +------------------------ Release 1.8 has come a long way again. Due to corona the project slowed down to a crawl. Fortunately new contributors have joined the team what enabled us to still release Mailu 1.8 this year. diff --git a/docs/requirements.txt b/docs/requirements.txt index 4afd9bb6..f49e26d5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,3 +2,4 @@ recommonmark Sphinx sphinx-autobuild sphinx-rtd-theme +docutils==0.16 diff --git a/docs/reverse.rst b/docs/reverse.rst index 29f9e9e1..14a9154e 100644 --- a/docs/reverse.rst +++ b/docs/reverse.rst @@ -3,14 +3,13 @@ Using an external reverse proxy One of Mailu use cases is as part of a larger services platform, where maybe other Web services are available than Mailu Webmail and admin interface. -In such a configuration, one would usually run a frontend reverse proxy to serve all Web contents based on criteria like the requested hostname (virtual hosts) and/or the requested path. Mailu Web frontend is disabled in the default setup for security reasons, it is however expected that most users will enable it at some point. Also, due to Docker Compose configuration structure, it is impossible for us to make disabling the Web frontend completely available through a configuration variable. This guide was written to help users setup such an architecture. +In such a configuration, one would usually run a frontend reverse proxy to serve all Web contents based on criteria like the requested hostname (virtual hosts) and/or the requested path. Mailu Admin Web frontend is disabled in the default setup for security reasons, it is however expected that most users will enable it at some point. Also, due to Docker Compose configuration structure, it is impossible for us to make disabling the Web frontend completely available through a configuration variable. This guide was written to help users setup such an architecture. There are basically three options, from the most to the least recommended one: -- have Mailu Web frontend listen locally and use your own Web frontend on top of it -- use ``Traefik`` in another container as central system-reverse-proxy -- override Mailu Web frontend configuration -- disable Mailu Web frontend completely and use your own +- `have Mailu Web frontend listen locally and use your own Web frontend on top of it`_ +- `use Traefik in another container as central system-reverse-proxy`_ +- `override Mailu Web frontend configuration`_ All options will require that you modify the ``docker-compose.yml`` file. @@ -89,7 +88,7 @@ Here is an example configuration : server { listen :443; - server_name yourpublicname.tld; + server_name external.example.com; # [...] here goes your standard configuration location /webmail { @@ -99,7 +98,7 @@ Here is an example configuration : server { listen :443; - server_name yourinternalname.tld; + server_name internal.example.com; # [...] here goes your standard configuration location /admin { @@ -113,7 +112,7 @@ Depending on how you access the front server, you might want to add a ``proxy_re .. code-block:: nginx - proxy_redirect https://localhost https://your-domain.com; + proxy_redirect https://localhost https://example.com; This will stop redirects (301 and 302) sent by the Webmail, nginx front and admin interface from sending you to ``localhost``. @@ -151,8 +150,8 @@ Add the respective Traefik labels for your domain/configuration, like .. note:: Please don’t forget to add ``TRAEFIK_DOMAIN=[...]`` TO YOUR ``.env`` -If your Traefik is configured to automatically request certificates from *letsencrypt*, then you’ll have a certificate for ``mail.your.doma.in`` now. However, -``mail.your.doma.in`` might only be the location where you want the Mailu web-interfaces to live — your mail should be sent/received from ``your.doma.in``, +If your Traefik is configured to automatically request certificates from *letsencrypt*, then you’ll have a certificate for ``mail.your.example.com`` now. However, +``mail.your.example.com`` might only be the location where you want the Mailu web-interfaces to live — your mail should be sent/received from ``your.example.com``, and this is the ``DOMAIN`` in your ``.env``? To support that use-case, Traefik can request ``SANs`` for your domain. The configuration for this will depend on your Traefik version. @@ -171,12 +170,12 @@ Add the appropriate labels for your domain(s) to the ``front`` container in ``do # Enable TLS - "traefik.http.routers.mailu-secure.tls" # Your main domain - - "traefik.http.routers.mailu-secure.tls.domains[0].main=your.doma.in" + - "traefik.http.routers.mailu-secure.tls.domains[0].main=your.example.com" # Optional SANs for your main domain - - "traefik.http.routers.mailu-secure.tls.domains[0].sans=mail.your.doma.in,webmail.your.doma.in,smtp.your.doma.in" + - "traefik.http.routers.mailu-secure.tls.domains[0].sans=mail.your.example.com,webmail.your.example.com,smtp.your.example.com" # Optionally add other domains - - "traefik.http.routers.mailu-secure.tls.domains[1].main=mail.other.doma.in" - - "traefik.http.routers.mailu-secure.tls.domains[1].sans=mail2.other.doma.in,mail3.other.doma.in" + - "traefik.http.routers.mailu-secure.tls.domains[1].main=mail.other.example.com" + - "traefik.http.routers.mailu-secure.tls.domains[1].sans=mail2.other.example.com,mail3.other.example.com" # Your ACME certificate resolver - "traefik.http.routers.mailu-secure.tls.certResolver=foo" @@ -193,8 +192,8 @@ Lets add something like [acme] [[acme.domains]] - main = "your.doma.in" # this is the same as $TRAEFIK_DOMAIN! - sans = ["mail.your.doma.in", "webmail.your.doma.in", "smtp.your.doma.in"] + main = "your.example.com" # this is the same as $TRAEFIK_DOMAIN! + sans = ["mail.your.example.com", "webmail.your.example.com", "smtp.your.example.com"] to your ``traefik.toml``. @@ -259,9 +258,7 @@ You can also download the example configuration files: - :download:`compose/traefik/docker-compose.yml` - :download:`compose/traefik/traefik.toml` -Disable completely Mailu reverse proxy --------------------------------------- +.. _have Mailu Web frontend listen locally and use your own Web frontend on top of it: #have-mailu-web-frontend-listen-locally +.. _use Traefik in another container as central system-reverse-proxy: #traefik-as-reverse-proxy +.. _override Mailu Web frontend configuration: #override-mailu-configuration -You must not disable Mailu reverse proxy by removing the ``front`` section from the ``docker-compose.yml``. - -``front`` is handling authentication and is also proxying e.g. SMTP and IMAP. A basic HTTP reverse proxy as described in this document is not sufficient for this. diff --git a/docs/webadministration.rst b/docs/webadministration.rst index 86ce41c0..03b07ba2 100644 --- a/docs/webadministration.rst +++ b/docs/webadministration.rst @@ -315,6 +315,21 @@ This page is also accessible for domain managers. On the users page new users ca * Fetched accounts. Access the fetched accounts page of the user. See the :ref:`fetched accounts page ` for more information. +This page also shows an overview of the following settings of an user: + +* Email. The email address of the user. + +* Features. Shows if IMAP or POP3 access is enabled. + +* Storage quota. Shows how much assigned storage has been consumed. + +* Sending Quota. The sending quota is the limit of messages a single user can send per day. + +* Comment. A desription for the user. + +* Created. Date when the user was created. + +* Last edit. Last date when the user was modified. .. _webadministration_add_user: @@ -334,7 +349,7 @@ For adding a new user the following options can be configured. * Enabled. Tick this checkbox to enable the user account. When an user is disabled, the user is unable to login to the Admin GUI or webmail or access his email via IMAP/POP3 or send mail. The email inbox of the user is still retained. This option can be used to temporarily suspend an user account. -* Quota. The maximum quota for the user's email box. +* Storage Quota. The maximum quota for the user's email box. * Allow IMAP access. When ticked, allows email retrieval via the IMAP protocol. diff --git a/optional/clamav/Dockerfile b/optional/clamav/Dockerfile index 1132845f..20cebcdc 100644 --- a/optional/clamav/Dockerfile +++ b/optional/clamav/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images RUN apk add --no-cache \ diff --git a/optional/fetchmail/Dockerfile b/optional/fetchmail/Dockerfile index a707a54a..506e409a 100644 --- a/optional/fetchmail/Dockerfile +++ b/optional/fetchmail/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images diff --git a/optional/postgresql/Dockerfile b/optional/postgresql/Dockerfile index ff25a66f..0f5034da 100644 --- a/optional/postgresql/Dockerfile +++ b/optional/postgresql/Dockerfile @@ -1,9 +1,8 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images RUN apk add --no-cache \ python3 py3-pip bash py3-multidict \ - && apk add --upgrade sudo \ && pip3 install --upgrade pip # Shared layer between nginx, dovecot, postfix, postgresql, rspamd, unbound, rainloop, roundcube diff --git a/optional/radicale/Dockerfile b/optional/radicale/Dockerfile index 400b1a3f..13761164 100644 --- a/optional/radicale/Dockerfile +++ b/optional/radicale/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.12 +ARG DISTRO=alpine:3.14 FROM $DISTRO # python3 shared with most images diff --git a/setup/Dockerfile b/setup/Dockerfile index 2b3c3c6c..5775ab6b 100644 --- a/setup/Dockerfile +++ b/setup/Dockerfile @@ -1,4 +1,4 @@ -ARG DISTRO=alpine:3.10 +ARG DISTRO=alpine:3.14 FROM $DISTRO RUN mkdir -p /app diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index d45f5517..52f4ee04 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -62,6 +62,11 @@ ANTIVIRUS={{ antivirus_enabled or 'none' }} # Max attachment size will be 33% smaller MESSAGE_SIZE_LIMIT={{ message_size_limit or '50000000' }} +# Message rate limit (per user) +{% if message_ratelimit_pd > '0' %} +MESSAGE_RATELIMIT={{ message_ratelimit_pd }}/day +{% endif %} + # Networks granted relay permissions # Use this with care, all hosts in this networks will be able to send mail without authentication! RELAYNETS= diff --git a/setup/requirements.txt b/setup/requirements.txt index b6bf2120..f2eb2157 100644 --- a/setup/requirements.txt +++ b/setup/requirements.txt @@ -1,4 +1,4 @@ -flask -flask-bootstrap -redis -gunicorn +Flask==1.0.2 +Flask-Bootstrap==3.3.7.1 +gunicorn==19.9.0 +redis==3.2.1 diff --git a/setup/server.py b/setup/server.py index 0d58fa25..5be1fc83 100644 --- a/setup/server.py +++ b/setup/server.py @@ -54,11 +54,11 @@ def build_app(path): @app.context_processor def app_context(): return dict( - versions=os.getenv("VERSIONS","master").split(','), + versions=os.getenv("VERSIONS","master").split(','), stable_version = os.getenv("stable_version", "master") ) - prefix_bp = flask.Blueprint(version, __name__) + prefix_bp = flask.Blueprint(version.replace(".", "_"), __name__) prefix_bp.jinja_loader = jinja2.ChoiceLoader([ jinja2.FileSystemLoader(os.path.join(path, "templates")), jinja2.FileSystemLoader(os.path.join(path, "flavors")) diff --git a/setup/templates/steps/compose/02_services.html b/setup/templates/steps/compose/02_services.html index 20d4d7cb..5118c304 100644 --- a/setup/templates/steps/compose/02_services.html +++ b/setup/templates/steps/compose/02_services.html @@ -59,7 +59,7 @@ the security implications caused by such an increase of attack surface.

Fetchmail allows users to retrieve mail from an external mail-server via IMAP/POP3 and puts it in their inbox.

- + diff --git a/setup/templates/steps/config.html b/setup/templates/steps/config.html index 29d8dddd..f532f757 100644 --- a/setup/templates/steps/config.html +++ b/setup/templates/steps/config.html @@ -50,8 +50,15 @@ Or in plain english: if receivers start to classify your mail as spam, this post
-

/ minute +

/ minute +

+
+ +
+ + +

/ day

@@ -83,7 +90,7 @@ manage your email domains, users, etc.

- + diff --git a/setup/templates/steps/stack/02_services.html b/setup/templates/steps/stack/02_services.html index 3f5186b0..6fce0ae6 100644 --- a/setup/templates/steps/stack/02_services.html +++ b/setup/templates/steps/stack/02_services.html @@ -55,7 +55,7 @@ the security implications caused by such an increase of attack surface.

Fetchmail allows users to retrieve mail from an external mail-server via IMAP/POP3 and puts it in their inbox.

- + diff --git a/tests/build_arm.sh b/tests/build_arm.sh index 04836ddb..32dba421 100755 --- a/tests/build_arm.sh +++ b/tests/build_arm.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -ALPINE_VER="3.10" +ALPINE_VER="3.14" DISTRO="balenalib/rpi-alpine:$ALPINE_VER" # Used for webmails QEMU="arm" diff --git a/tests/deploy.sh b/tests/deploy.sh index a836417b..abb37b6b 100755 --- a/tests/deploy.sh +++ b/tests/deploy.sh @@ -3,14 +3,5 @@ # Skip deploy for staging branch [ "$TRAVIS_BRANCH" = "staging" ] && exit 0 -# Retag in case of `bors try` -if [ "$TRAVIS_BRANCH" = "testing" ]; then - export DOCKER_ORG=$DOCKER_ORG_TESTS - # Commit message is like "Try #99". - # This sets the version tag to "pr-99" - export MAILU_VERSION="pr-${TRAVIS_COMMIT_MESSAGE//[!0-9]/}" - docker-compose -f tests/build.yml build -fi - docker login -u $DOCKER_UN -p $DOCKER_PW docker-compose -f tests/build.yml push diff --git a/towncrier/newsfragments/1031.feature b/towncrier/newsfragments/1031.feature new file mode 100644 index 00000000..5f369262 --- /dev/null +++ b/towncrier/newsfragments/1031.feature @@ -0,0 +1 @@ +Add sending quotas per user diff --git a/towncrier/newsfragments/1096.feature b/towncrier/newsfragments/1096.feature new file mode 100644 index 00000000..f3abd3dc --- /dev/null +++ b/towncrier/newsfragments/1096.feature @@ -0,0 +1 @@ +Allow specific users to send emails from any address using the WILDCARD_SENDERS setting diff --git a/towncrier/newsfragments/1294.bugfix b/towncrier/newsfragments/1294.bugfix new file mode 100644 index 00000000..68bb7a8a --- /dev/null +++ b/towncrier/newsfragments/1294.bugfix @@ -0,0 +1 @@ +Ensure that the podop socket is always owned by the postfix user (wasn't the case when build using non-standard base images... typically for arm64) diff --git a/towncrier/newsfragments/1558.feature b/towncrier/newsfragments/1558.feature new file mode 100644 index 00000000..5c4ec30f --- /dev/null +++ b/towncrier/newsfragments/1558.feature @@ -0,0 +1 @@ +Make smtp_tls_policy_maps easily configurable diff --git a/towncrier/newsfragments/1567.feature b/towncrier/newsfragments/1567.feature new file mode 100644 index 00000000..8dc1515a --- /dev/null +++ b/towncrier/newsfragments/1567.feature @@ -0,0 +1 @@ +Implement a language selector for the admin interface. diff --git a/towncrier/newsfragments/1660.bugfix b/towncrier/newsfragments/1660.bugfix deleted file mode 100644 index a90fb099..00000000 --- a/towncrier/newsfragments/1660.bugfix +++ /dev/null @@ -1 +0,0 @@ -Don't replace nested headers (typically in attached emails) diff --git a/towncrier/newsfragments/1686.bugfix b/towncrier/newsfragments/1686.bugfix deleted file mode 100644 index 932d7d7c..00000000 --- a/towncrier/newsfragments/1686.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix letsencrypt access to certbot for the mail-letsencrypt flavour diff --git a/towncrier/newsfragments/1720.bugfix b/towncrier/newsfragments/1720.bugfix deleted file mode 100644 index 0bf2b8e6..00000000 --- a/towncrier/newsfragments/1720.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Fix CVE-2020-25275 and CVE-2020-24386 by using alpine 3.13 for -dovecot which contains a fixed dovecot version. diff --git a/towncrier/newsfragments/1764.feature b/towncrier/newsfragments/1764.feature new file mode 100644 index 00000000..7eb51624 --- /dev/null +++ b/towncrier/newsfragments/1764.feature @@ -0,0 +1 @@ +Implement AdminLTE 3 for the admin interface. diff --git a/towncrier/newsfragments/1783.misc b/towncrier/newsfragments/1783.misc deleted file mode 100644 index 2ee4c97f..00000000 --- a/towncrier/newsfragments/1783.misc +++ /dev/null @@ -1 +0,0 @@ -Switch from client side sessions (cookies) to server-side sessions (Redis). This simplies the security model a lot and allows for an easier recovery should a cookie ever land in the hands of an attacker. diff --git a/towncrier/newsfragments/1837.bugfix b/towncrier/newsfragments/1837.bugfix deleted file mode 100644 index dcabcc6b..00000000 --- a/towncrier/newsfragments/1837.bugfix +++ /dev/null @@ -1 +0,0 @@ -Antispam service now uses a static hostname. Rspamd history is only retained when the service has a fixed hostname. diff --git a/towncrier/newsfragments/1841.feature b/towncrier/newsfragments/1841.feature deleted file mode 100644 index c91f805f..00000000 --- a/towncrier/newsfragments/1841.feature +++ /dev/null @@ -1 +0,0 @@ -Update version of roundcube webmail and carddav plugin. This is a security update. \ No newline at end of file diff --git a/towncrier/newsfragments/1851.feature b/towncrier/newsfragments/1851.feature new file mode 100644 index 00000000..e01f5cb4 --- /dev/null +++ b/towncrier/newsfragments/1851.feature @@ -0,0 +1 @@ +Remove cyrus-sasl-plain as it's not packaged by alpine anymore. SASL-login is still available and used when relaying. diff --git a/towncrier/newsfragments/1873.feature b/towncrier/newsfragments/1873.feature new file mode 100644 index 00000000..dacf117e --- /dev/null +++ b/towncrier/newsfragments/1873.feature @@ -0,0 +1 @@ + Hebrew translation has been completed. diff --git a/towncrier/newsfragments/1917.bugfix b/towncrier/newsfragments/1917.bugfix new file mode 100644 index 00000000..68187d61 --- /dev/null +++ b/towncrier/newsfragments/1917.bugfix @@ -0,0 +1 @@ +Alpine has removed support for btree and hash in postfix... please use lmdb instead diff --git a/towncrier/newsfragments/1922.enhancement b/towncrier/newsfragments/1922.enhancement new file mode 100644 index 00000000..4b01fa88 --- /dev/null +++ b/towncrier/newsfragments/1922.enhancement @@ -0,0 +1,5 @@ +Add support for ECDSA certificates when letsencrypt is used. This means dropping compatibility for android < 4.1.1 +Add LETSENCRYPT_SHORTCHAIN to your configuration to avoid sending ISRG Root X1 (this will break compatibility with android < 7.1.1) +Disable AUTH command on port 25 +Disable TLS tickets, reconfigure the cache to improve Forward Secrecy +Prevent clear-text credentials from being sent to relays diff --git a/towncrier/newsfragments/1952.bugfix b/towncrier/newsfragments/1952.bugfix new file mode 100644 index 00000000..655715f1 --- /dev/null +++ b/towncrier/newsfragments/1952.bugfix @@ -0,0 +1,3 @@ +Webmail and Radicale (webdav) were not useable with domains with special characters such as umlauts. +Webmail and radicale now use punycode for logging in. +Punycode was not used in the HTTP headers. This resulted in illegal non-ASCII HTTP headers. diff --git a/towncrier/newsfragments/1960.bugfix b/towncrier/newsfragments/1960.bugfix new file mode 100644 index 00000000..ecf5ac50 --- /dev/null +++ b/towncrier/newsfragments/1960.bugfix @@ -0,0 +1 @@ +Ensure that we do not trust the source-ip address set in headers if REAL_IP_HEADER isn't set. If you are using Mailu behind a reverse proxy, please ensure that you do read the documentation. diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index 9987330e..f1394d64 100644 --- a/webmails/rainloop/Dockerfile +++ b/webmails/rainloop/Dockerfile @@ -1,51 +1,71 @@ ARG ARCH="" -ARG QEMU=other # NOTE: only add file if building for arm -FROM ${ARCH}php:7.4-apache as build_arm -ONBUILD COPY --from=balenalib/rpi-alpine:3.10 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static +FROM ${ARCH}alpine:3.14 +ONBUILD COPY --from=balenalib/rpi-alpine:3.14 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static -FROM ${ARCH}php:7.4-apache as build_other +# Shared later between dovecot postfix nginx rspamd rainloop and roundloop +RUN apk add --no-cache \ + python3 py3-pip \ + && pip3 install socrate==0.2.0 -FROM build_${QEMU} -#Shared layer between rainloop and roundcube -RUN apt-get update && apt-get install -y \ - python3 curl python3-pip git python3-multidict \ - && rm -rf /var/lib/apt/lists \ - && echo "ServerSignature Off" >> /etc/apache2/apache2.conf +# https://www.rainloop.net/docs/system-requirements/ +# Rainloop: +# cURL Builtin +# iconv php7-iconv +# json php7-json +# libxml php7-xml +# dom php7-dom +# openssl php7-openssl +# DateTime Builtin +# PCRE Builtin +# SPL Builtin +# Recommended: +# php7-fpm FastCGI Process Manager +# Optional PHP extension (for contacts): +# php7-pdo Accessing databases in PHP +# php7-pdo_sqlite Access to SQLite 3 databases +RUN apk add --no-cache \ + nginx \ + php7 php7-fpm php7-curl php7-iconv php7-json php7-xml php7-dom php7-openssl php7-pdo php7-pdo_sqlite \ + && rm /etc/nginx/http.d/default.conf \ + && rm /etc/php7/php-fpm.d/www.conf \ + && mkdir -p /run/nginx \ + && mkdir -p /var/www/rainloop -# Shared layer between nginx, dovecot, postfix, postgresql, rspamd, unbound, rainloop, roundcube -RUN pip3 install socrate +# nginx / PHP config files +COPY config/nginx-rainloop.conf /etc/nginx/http.d/rainloop.conf +COPY config/php-rainloop.conf /etc/php7/php-fpm.d/rainloop.conf -ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.14.0/rainloop-community-1.14.0.zip +# Rainloop login +COPY login/include.php /var/www/rainloop/include.php +COPY login/sso.php /var/www/rainloop/sso.php -RUN apt-get update && apt-get install -y \ - unzip python3-jinja2 \ - && rm -rf /var/www/html/ \ - && mkdir /var/www/html \ - && cd /var/www/html \ +# Parsed en moved at startup +COPY defaults/php.ini /defaults/php.ini +COPY defaults/application.ini /defaults/application.ini +COPY defaults/default.ini /defaults/default.ini + +# Install Rainloop from source +ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.16.0/rainloop-community-1.16.0.zip + +RUN apk add --no-cache \ + curl unzip \ + && cd /var/www/rainloop \ && curl -L -O ${RAINLOOP_URL} \ && unzip -q *.zip \ && rm -f *.zip \ && rm -rf data/ \ && find . -type d -exec chmod 755 {} \; \ && find . -type f -exec chmod 644 {} \; \ - && chown -R www-data: * \ - && apt-get purge -y unzip \ - && rm -rf /var/lib/apt/lists - -COPY include.php /var/www/html/include.php -COPY sso.php /var/www/html/sso.php -COPY php.ini /php.ini - -COPY application.ini /application.ini -COPY default.ini /default.ini + && chown -R nginx:nginx /var/www/rainloop \ + && apk del unzip COPY start.py /start.py EXPOSE 80/tcp VOLUME ["/data"] -CMD /start.py +CMD php-fpm7 && /start.py HEALTHCHECK CMD curl -f -L http://localhost/ || exit 1 diff --git a/webmails/rainloop/config/nginx-rainloop.conf b/webmails/rainloop/config/nginx-rainloop.conf new file mode 100644 index 00000000..dfdbf8f7 --- /dev/null +++ b/webmails/rainloop/config/nginx-rainloop.conf @@ -0,0 +1,38 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/rainloop; + + # /dev/stdout (Default), , off + access_log off; + + # /dev/stderr (Default), , debug, info, notice, warn, error, crit, alert, emerg + error_log /dev/stderr warn; + + index index.php; + + location / { + try_files $uri /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.*)$; + + fastcgi_intercept_errors on; + fastcgi_index index.php; + + fastcgi_keep_conn on; + include /etc/nginx/fastcgi_params; + fastcgi_pass unix:/var/run/php7-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location ~ /\.ht { + deny all; + } + + location ^~ /data { + deny all; + } +} diff --git a/webmails/rainloop/config/php-rainloop.conf b/webmails/rainloop/config/php-rainloop.conf new file mode 100644 index 00000000..e9906505 --- /dev/null +++ b/webmails/rainloop/config/php-rainloop.conf @@ -0,0 +1,101 @@ +; Start a new pool named 'rainloop'. +; the variable $pool can be used in any directive and will be replaced by the +; pool name ('rainloop' here) +[rainloop] + +; Redirect worker stdout and stderr into main error log. If not set, stdout and +; stderr will be redirected to /dev/null according to FastCGI specs. +; Default value: no. +catch_workers_output = 1 + +; Unix user/group of processes +; Note: The user is mandatory. If the group is not set, the default user's group +; will be used. +user = nginx +group = nginx + +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on +; a specific port; +; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses +; (IPv6 and IPv4-mapped) on a specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +listen = /var/run/php7-fpm.sock + +; Set permissions for unix socket, if one is used. In Linux, read/write +; permissions must be set in order to allow connections from a web server. Many +; BSD-derived systems allow connections regardless of permissions. +; Default Values: user and group are set as the running user +; mode is set to 0660 +listen.owner = nginx +listen.group = nginx +listen.mode = 0660 + +; Choose how the process manager will control the number of child processes. +; Possible Values: +; static - a fixed number (pm.max_children) of child processes; +; dynamic - the number of child processes are set dynamically based on the +; following directives. With this process management, there will be +; always at least 1 children. +; pm.max_children - the maximum number of children that can +; be alive at the same time. +; pm.start_servers - the number of children created on startup. +; pm.min_spare_servers - the minimum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is less than this +; number then some children will be created. +; pm.max_spare_servers - the maximum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is greater than this +; number then some children will be killed. +; ondemand - no children are created at startup. Children will be forked when +; new requests will connect. The following parameter are used: +; pm.max_children - the maximum number of children that +; can be alive at the same time. +; pm.process_idle_timeout - The number of seconds after which +; an idle process will be killed. +; Note: This value is mandatory. +pm = ondemand + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 5 + +; The number of child processes created on startup. +; Note: Used only when pm is set to 'dynamic' +; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 +; pm.start_servers = 2 + +; The desired minimum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +; pm.min_spare_servers = 1 + +; The desired maximum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +; pm.max_spare_servers = 3 + +; This sets the maximum time in seconds a script is allowed to run before it is +; terminated by the parser. This helps prevent poorly written scripts from tying up +; the server. The default setting is 30s. +; Note: Used only when pm is set to 'ondemand' +pm.process_idle_timeout = 10s + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For endless +; request processing specify '0'. +; Equivalent to PHP_FCGI_MAX_REQUESTS. Default value: 0. +; Noted: Used only when pm is set to 'ondemand' +pm.max_requests = 200 diff --git a/webmails/rainloop/application.ini b/webmails/rainloop/defaults/application.ini similarity index 100% rename from webmails/rainloop/application.ini rename to webmails/rainloop/defaults/application.ini diff --git a/webmails/rainloop/default.ini b/webmails/rainloop/defaults/default.ini similarity index 100% rename from webmails/rainloop/default.ini rename to webmails/rainloop/defaults/default.ini diff --git a/webmails/rainloop/php.ini b/webmails/rainloop/defaults/php.ini similarity index 100% rename from webmails/rainloop/php.ini rename to webmails/rainloop/defaults/php.ini diff --git a/webmails/rainloop/include.php b/webmails/rainloop/login/include.php similarity index 100% rename from webmails/rainloop/include.php rename to webmails/rainloop/login/include.php diff --git a/webmails/rainloop/sso.php b/webmails/rainloop/login/sso.php similarity index 89% rename from webmails/rainloop/sso.php rename to webmails/rainloop/login/sso.php index 2415f45c..0bfbe263 100644 --- a/webmails/rainloop/sso.php +++ b/webmails/rainloop/login/sso.php @@ -18,7 +18,7 @@ if (file_exists(APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/include.php')) { } // Retrieve email and password -if (in_array('HTTP_X_REMOTE_USER', $_SERVER) && in_array('HTTP_X_REMOTE_USER_TOKEN', $_SERVER)) { +if (isset($_SERVER['HTTP_X_REMOTE_USER']) && isset($_SERVER['HTTP_X_REMOTE_USER_TOKEN'])) { $email = $_SERVER['HTTP_X_REMOTE_USER']; $password = $_SERVER['HTTP_X_REMOTE_USER_TOKEN']; $ssoHash = \RainLoop\Api::GetUserSsoHash($email, $password); diff --git a/webmails/rainloop/start.py b/webmails/rainloop/start.py index 2d537284..a52b70e9 100755 --- a/webmails/rainloop/start.py +++ b/webmails/rainloop/start.py @@ -19,12 +19,11 @@ shutil.rmtree(base + "domains/", ignore_errors=True) os.makedirs(base + "domains", exist_ok=True) os.makedirs(base + "configs", exist_ok=True) -conf.jinja("/default.ini", os.environ, "/data/_data_/_default_/domains/default.ini") -conf.jinja("/application.ini", os.environ, "/data/_data_/_default_/configs/application.ini") -conf.jinja("/php.ini", os.environ, "/usr/local/etc/php/conf.d/rainloop.ini") +conf.jinja("/defaults/default.ini", os.environ, "/data/_data_/_default_/domains/default.ini") +conf.jinja("/defaults/application.ini", os.environ, "/data/_data_/_default_/configs/application.ini") +conf.jinja("/defaults/php.ini", os.environ, "/etc/php7/php.ini") -os.system("chown -R www-data:www-data /data") -os.system("chmod -R a+rX /var/www/html/") - -os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"]) +os.system("chown -R nginx:nginx /data") +os.system("chmod -R a+rX /var/www/rainloop/") +os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"]) diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index fae02ce0..4d3e36df 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -2,7 +2,7 @@ ARG ARCH="" ARG QEMU=other FROM ${ARCH}php:7.4-apache as build_arm -ONBUILD COPY --from=balenalib/rpi-alpine:3.10 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static +ONBUILD COPY --from=balenalib/rpi-alpine:3.14 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static FROM ${ARCH}php:7.4-apache as build_other