mirror of
https://github.com/Mailu/Mailu.git
synced 2025-06-04 23:27:34 +02:00
Merge branch 'master' into import-export
This commit is contained in:
commit
6dc1a19390
318
.github/workflows/CI.yml
vendored
Normal file
318
.github/workflows/CI.yml
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- staging
|
||||
- testing
|
||||
- '1.5'
|
||||
- '1.6'
|
||||
- '1.7'
|
||||
- '1.8'
|
||||
- master
|
||||
# version tags, e.g. 1.7.1
|
||||
- '[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-*
|
||||
|
||||
###############################################
|
||||
# REQUIRED secrets
|
||||
# DOCKER_UN: ${{ secrets.Docker_Login }}
|
||||
# Username of docker login for pushing the images to repo $DOCKER_ORG
|
||||
# DOCKER_PW: ${{ secrets.Docker_Password }}
|
||||
# Password of docker login for pushing the images to repo $DOCKER_ORG
|
||||
# DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
# The docker repository where the images are pushed to.
|
||||
# DOCKER_ORG_TESTS: ${{ secrets.DOCKER_ORG_TESTS }}
|
||||
# The docker repository for test images. Only used for the branch TESTING (BORS try).
|
||||
# Add the above secrets to your github repo to determine where the images will be pushed.
|
||||
################################################
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Check docker-compose version
|
||||
run: docker-compose -v
|
||||
- name: Login docker
|
||||
env:
|
||||
DOCKER_UN: ${{ secrets.Docker_Login }}
|
||||
DOCKER_PW: ${{ secrets.Docker_Password }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
run: echo "$DOCKER_PW" | docker login --username $DOCKER_UN --password-stdin
|
||||
- name: Build all docker images
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
run: docker-compose -f tests/build.yml build
|
||||
- name: Save all docker images
|
||||
run: docker save ${{ secrets.DOCKER_ORG }}/admin ${{ secrets.DOCKER_ORG }}/clamav ${{ secrets.DOCKER_ORG }}/docs ${{ secrets.DOCKER_ORG }}/dovecot ${{ secrets.DOCKER_ORG }}/fetchmail ${{ secrets.DOCKER_ORG }}/nginx ${{ secrets.DOCKER_ORG }}/none ${{ secrets.DOCKER_ORG }}/postfix ${{ secrets.DOCKER_ORG }}/postgresql ${{ secrets.DOCKER_ORG }}/radicale ${{ secrets.DOCKER_ORG }}/rainloop ${{ secrets.DOCKER_ORG }}/roundcube ${{ secrets.DOCKER_ORG }}/rspamd ${{ secrets.DOCKER_ORG }}/setup ${{ secrets.DOCKER_ORG }}/traefik-certdumper ${{ secrets.DOCKER_ORG }}/unbound -o /images/images.tar.gz
|
||||
|
||||
test-core:
|
||||
name: Perform core tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Install python packages
|
||||
run: python3 -m pip install -r tests/requirements.txt
|
||||
- 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
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
|
||||
test-fetchmail:
|
||||
name: Perform fetchmail tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Install python packages
|
||||
run: python3 -m pip install -r tests/requirements.txt
|
||||
- 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
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
|
||||
test-filters:
|
||||
name: Perform filter tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Install python packages
|
||||
run: python3 -m pip install -r tests/requirements.txt
|
||||
- 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
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
|
||||
test-rainloop:
|
||||
name: Perform rainloop tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Install python packages
|
||||
run: python3 -m pip install -r tests/requirements.txt
|
||||
- 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
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
|
||||
test-roundcube:
|
||||
name: Perform roundcube tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Install python packages
|
||||
run: python3 -m pip install -r tests/requirements.txt
|
||||
- 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
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
|
||||
test-webdav:
|
||||
name: Perform webdav tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Install python packages
|
||||
run: python3 -m pip install -r tests/requirements.txt
|
||||
- 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
|
||||
env:
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
|
||||
deploy:
|
||||
name: Deploy images
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- test-core
|
||||
- test-fetchmail
|
||||
- test-filters
|
||||
- test-rainloop
|
||||
- test-roundcube
|
||||
- test-webdav
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
||||
- name: Create folder for storing images
|
||||
run: |
|
||||
sudo mkdir -p /images
|
||||
sudo chmod 777 /images
|
||||
- name: Configure images folder for caching
|
||||
# For staging we do not deploy images. So we do not have to load them from cache.
|
||||
if: ${{ env.BRANCH != 'staging' }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /images
|
||||
key: ${{ env.BRANCH }}-${{ github.run_id }}-${{ github.run_number }}
|
||||
- name: Load docker images
|
||||
if: ${{ env.BRANCH != 'staging' }}
|
||||
run: docker load -i /images/images.tar.gz
|
||||
- name: Deploy built docker images
|
||||
env:
|
||||
DOCKER_UN: ${{ secrets.Docker_Login }}
|
||||
DOCKER_PW: ${{ secrets.Docker_Password }}
|
||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||
DOCKER_ORG_TESTS: ${{ secrets.DOCKER_ORG_TESTS }}
|
||||
MAILU_VERSION: ${{ env.BRANCH }}
|
||||
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.
|
||||
ci-success:
|
||||
name: CI-Done
|
||||
#Returns true when none of the **previous** steps have failed or been canceled.
|
||||
if: ${{ success() }}
|
||||
needs:
|
||||
- deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: CI/CD succeeded.
|
||||
run: exit 0
|
@ -27,7 +27,7 @@ pull_request_rules:
|
||||
|
||||
- name: Trusted author and 1 approved review; trigger bors r+
|
||||
conditions:
|
||||
- author~=^(mergify|kaiyou|muhlemmer|mildred|HorayNarea|adi90x|hoellen|ofthesun9|Nebukadneza|micw|lub|Diman0)$
|
||||
- author~=^(mergify|kaiyou|muhlemmer|mildred|HorayNarea|hoellen|ofthesun9|Nebukadneza|micw|lub|Diman0|3-w-c|decentral1se|ghostwheel42|nextgens|parisni)$
|
||||
- -title~=(WIP|wip)
|
||||
- -label~=^(status/wip|status/blocked|review/need2)$
|
||||
- "#approved-reviews-by>=1"
|
||||
|
56
.travis.yml
56
.travis.yml
@ -1,56 +0,0 @@
|
||||
branches:
|
||||
only:
|
||||
- staging
|
||||
- testing
|
||||
- '1.5'
|
||||
- '1.6'
|
||||
- '1.7'
|
||||
- '1.8'
|
||||
- master
|
||||
# version tags, e.g. 1.7.1
|
||||
- /^1\.[5678]\.\d+$/
|
||||
# pre-releases, e.g. 1.8-pre1
|
||||
- /^1\.8-pre\d+$/
|
||||
# test branches, e.g. test-debian
|
||||
- /^test-[\w\-\.]+$/
|
||||
|
||||
sudo: required
|
||||
services: docker
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- docker-ce
|
||||
|
||||
env:
|
||||
- MAILU_VERSION=${TRAVIS_BRANCH////-}
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
install:
|
||||
- pip install -r tests/requirements.txt
|
||||
- sudo curl -L https://github.com/docker/compose/releases/download/1.23.0-rc3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
||||
- sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
before_script:
|
||||
- docker-compose -v
|
||||
- echo "$DOCKER_PW" | docker login --username $DOCKER_UN --password-stdin
|
||||
- docker-compose -f tests/build.yml build
|
||||
- sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*'
|
||||
|
||||
|
||||
script:
|
||||
# test.py, test name and timeout between start and tests.
|
||||
- python tests/compose/test.py core 1
|
||||
- python tests/compose/test.py fetchmail 1
|
||||
- travis_wait python tests/compose/test.py filters 10
|
||||
- python tests/compose/test.py rainloop 1
|
||||
- python tests/compose/test.py roundcube 1
|
||||
- python tests/compose/test.py webdav 1
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: bash tests/deploy.sh
|
||||
on:
|
||||
all_branches: true
|
||||
condition: -n $DOCKER_UN
|
@ -6,6 +6,7 @@ from simplekv.memory.redisstore import RedisStore
|
||||
|
||||
from mailu import utils, debug, models, manage, configuration
|
||||
|
||||
import hmac
|
||||
|
||||
def create_app_from_config(config):
|
||||
""" Create a new application based on the given configuration
|
||||
@ -28,6 +29,8 @@ def create_app_from_config(config):
|
||||
utils.proxy.init_app(app)
|
||||
utils.migrate.init_app(app, models.db)
|
||||
|
||||
app.temp_token_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('WEBMAIL_TEMP_TOKEN_KEY', 'utf-8'), 'sha256').digest()
|
||||
|
||||
# Initialize debugging tools
|
||||
if app.config.get("DEBUG"):
|
||||
debug.toolbar.init_app(app)
|
||||
|
@ -7,7 +7,6 @@ import ipaddress
|
||||
import socket
|
||||
import tenacity
|
||||
|
||||
|
||||
SUPPORTED_AUTH_METHODS = ["none", "plain"]
|
||||
|
||||
|
||||
@ -26,8 +25,12 @@ def check_credentials(user, password, ip, protocol=None):
|
||||
if not user or not user.enabled or (protocol == "imap" and not user.enable_imap) or (protocol == "pop3" and not user.enable_pop):
|
||||
return False
|
||||
is_ok = False
|
||||
# webmails
|
||||
if len(password) == 64 and ip == app.config['WEBMAIL_ADDRESS']:
|
||||
if user.verify_temp_token(password):
|
||||
is_ok = True
|
||||
# All tokens are 32 characters hex lowercase
|
||||
if len(password) == 32:
|
||||
if not is_ok and len(password) == 32:
|
||||
for token in user.tokens:
|
||||
if (token.check_password(password) and
|
||||
(not token.ip or token.ip == ip)):
|
||||
|
@ -43,6 +43,18 @@ def admin_authentication():
|
||||
return ""
|
||||
return flask.abort(403)
|
||||
|
||||
@internal.route("/auth/user")
|
||||
def user_authentication():
|
||||
""" Fails if the user is not authenticated.
|
||||
"""
|
||||
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-Token"] = models.User.get_temp_token(flask_login.current_user.get_id())
|
||||
return response
|
||||
return flask.abort(403)
|
||||
|
||||
|
||||
@internal.route("/auth/basic")
|
||||
def basic_authentication():
|
||||
|
@ -2,6 +2,7 @@ from mailu import models
|
||||
from mailu.internal import internal
|
||||
|
||||
import flask
|
||||
import idna
|
||||
import re
|
||||
import srslib
|
||||
|
||||
@ -35,13 +36,67 @@ def postfix_alias_map(alias):
|
||||
def postfix_transport(email):
|
||||
if email == '*' or re.match("(^|.*@)\[.*\]$", email):
|
||||
return flask.abort(404)
|
||||
localpart, domain_name = models.Email.resolve_domain(email)
|
||||
_, domain_name = models.Email.resolve_domain(email)
|
||||
relay = models.Relay.query.get(domain_name) or flask.abort(404)
|
||||
ret = "smtp:[{0}]".format(relay.smtp)
|
||||
if ":" in relay.smtp:
|
||||
split = relay.smtp.split(':')
|
||||
ret = "smtp:[{0}]:{1}".format(split[0], split[1])
|
||||
return flask.jsonify(ret)
|
||||
target = relay.smtp.lower()
|
||||
port = None
|
||||
use_lmtp = False
|
||||
use_mx = False
|
||||
# strip prefixes mx: and lmtp:
|
||||
if target.startswith('mx:'):
|
||||
target = target[3:]
|
||||
use_mx = True
|
||||
elif target.startswith('lmtp:'):
|
||||
target = target[5:]
|
||||
use_lmtp = True
|
||||
# split host:port or [host]:port
|
||||
if target.startswith('['):
|
||||
if use_mx or ']' not in target:
|
||||
# invalid target (mx: and [] or missing ])
|
||||
flask.abort(400)
|
||||
host, rest = target[1:].split(']', 1)
|
||||
if rest.startswith(':'):
|
||||
port = rest[1:]
|
||||
elif rest:
|
||||
# invalid target (rest should be :port)
|
||||
flask.abort(400)
|
||||
else:
|
||||
if ':' in target:
|
||||
host, port = target.rsplit(':', 1)
|
||||
else:
|
||||
host = target
|
||||
# default for empty host part is mx:domain
|
||||
if not host:
|
||||
if not use_lmtp:
|
||||
host = relay.name.lower()
|
||||
use_mx = True
|
||||
else:
|
||||
# lmtp: needs a host part
|
||||
flask.abort(400)
|
||||
# detect ipv6 address or encode host
|
||||
if ':' in host:
|
||||
host = f'ipv6:{host}'
|
||||
else:
|
||||
try:
|
||||
host = idna.encode(host).decode('ascii')
|
||||
except idna.IDNAError:
|
||||
# invalid host (fqdn not encodable)
|
||||
flask.abort(400)
|
||||
# validate port
|
||||
if port is not None:
|
||||
try:
|
||||
port = int(port, 10)
|
||||
except ValueError:
|
||||
# invalid port (should be numeric)
|
||||
flask.abort(400)
|
||||
# create transport
|
||||
transport = 'lmtp' if use_lmtp else 'smtp'
|
||||
# use [] when not using MX lookups or host is an ipv6 address
|
||||
if host.startswith('ipv6:') or (not use_lmtp and not use_mx):
|
||||
host = f'[{host}]'
|
||||
# create port suffix
|
||||
port = '' if port is None else f':{port}'
|
||||
return flask.jsonify(f'{transport}:{host}{port}')
|
||||
|
||||
|
||||
@internal.route("/postfix/recipient/map/<path:recipient>")
|
||||
|
@ -15,6 +15,11 @@ import sqlalchemy
|
||||
import passlib.context
|
||||
import passlib.hash
|
||||
import passlib.registry
|
||||
import time
|
||||
import os
|
||||
import glob
|
||||
import hmac
|
||||
import smtplib
|
||||
import idna
|
||||
import dns
|
||||
|
||||
@ -436,6 +441,7 @@ class User(Base, Email):
|
||||
|
||||
__tablename__ = 'user'
|
||||
_ctx = None
|
||||
_credential_cache = {}
|
||||
|
||||
domain = db.relationship(Domain,
|
||||
backref=db.backref('users', cascade='all, delete-orphan'))
|
||||
@ -522,6 +528,16 @@ class User(Base, Email):
|
||||
""" verifies password against stored hash
|
||||
and updates hash if outdated
|
||||
"""
|
||||
cache_result = self._credential_cache.get(self.get_id())
|
||||
current_salt = self.password.split('$')[3] if len(self.password.split('$')) == 5 else None
|
||||
if cache_result and current_salt:
|
||||
cache_salt, cache_hash = cache_result
|
||||
if cache_salt == current_salt:
|
||||
return hash.pbkdf2_sha256.verify(password, cache_hash)
|
||||
else:
|
||||
# the cache is local per gunicorn; the password has changed
|
||||
# so the local cache can be invalidated
|
||||
del self._credential_cache[self.get_id()]
|
||||
reference = self.password
|
||||
# strip {scheme} if that's something mailu has added
|
||||
# passlib will identify *crypt based hashes just fine
|
||||
@ -534,6 +550,17 @@ class User(Base, Email):
|
||||
self.password = new_hash
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
if result:
|
||||
"""The credential cache uses a low number of rounds to be fast.
|
||||
While it's not meant to be persisted to cold-storage, no additional measures
|
||||
are taken to ensure it isn't (mlock(), encrypted swap, ...) on the basis that
|
||||
we have little control over GC and string interning anyways.
|
||||
|
||||
An attacker that can dump the process' memory is likely to find credentials
|
||||
in clear-text regardless of the presence of the cache.
|
||||
"""
|
||||
self._credential_cache[self.get_id()] = (self.password.split('$')[3], hash.pbkdf2_sha256.using(rounds=1).hash(password))
|
||||
return result
|
||||
|
||||
def set_password(self, password, raw=False):
|
||||
@ -574,6 +601,15 @@ class User(Base, Email):
|
||||
user = cls.query.get(email)
|
||||
return user if (user and user.enabled and user.check_password(password)) else None
|
||||
|
||||
@classmethod
|
||||
def get_temp_token(cls, email):
|
||||
user = cls.query.get(email)
|
||||
return hmac.new(app.temp_token_key, bytearray("{}|{}".format(datetime.utcnow().strftime("%Y%m%d"), email), 'utf-8'), 'sha256').hexdigest() if (user and user.enabled) else None
|
||||
|
||||
def verify_temp_token(self, token):
|
||||
return hmac.compare_digest(self.get_temp_token(self.email), token)
|
||||
|
||||
|
||||
|
||||
class Alias(Base, Email):
|
||||
""" An alias is an email address that redirects to some destination.
|
||||
|
@ -528,7 +528,7 @@ msgstr "Alternatieve naam"
|
||||
|
||||
#: mailu/ui/forms.py:70
|
||||
msgid "Relayed domain name"
|
||||
msgstr "Relayed domainnaam"
|
||||
msgstr "Relayed domeinnaam"
|
||||
|
||||
#: mailu/ui/forms.py:71 mailu/ui/templates/relay/list.html:18
|
||||
msgid "Remote host"
|
||||
@ -536,7 +536,7 @@ msgstr "Externe host"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:54
|
||||
msgid "Relayed domains"
|
||||
msgstr "Relayed domainen"
|
||||
msgstr "Relayed domeinen"
|
||||
|
||||
#: mailu/ui/templates/alternative/create.html:4
|
||||
msgid "Create alternative domain"
|
||||
|
@ -50,5 +50,17 @@
|
||||
<td><pre>_dmarc.{{ domain.name }}. 600 IN TXT "v=DMARC1; p=reject;{% if config["DMARC_RUA"] %} rua=mailto:{{ config["DMARC_RUA"] }}@{{ config["DOMAIN"] }};{% endif %}{% if config["DMARC_RUF"] %} ruf=mailto:{{ config["DMARC_RUF"] }}@{{ config["DOMAIN"] }};{% endif %} adkim=s; aspf=s"</pre></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>{% trans %}DNS client auto-configuration (RFC6186) entries{% endtrans %}</th>
|
||||
<td>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_submission._tcp.{{ domain.name }}. 600 IN SRV 1 1 587 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_imap._tcp.{{ domain.name }}. 600 IN SRV 100 1 143 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_pop3._tcp.{{ domain.name }}. 600 IN SRV 100 1 110 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
{% if config["TLS_FLAVOR"] != "notls" %}
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_submissions._tcp.{{ domain.name }}. 600 IN SRV 10 1 465 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_imaps._tcp.{{ domain.name }}. 600 IN SRV 10 1 993 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">_pop3s._tcp.{{ domain.name }}. 600 IN SRV 10 1 995 {{ config["HOSTNAMES"].split(',')[0] }}.</pre>
|
||||
{% endif %}</td>
|
||||
</tr>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
@ -1,6 +1,7 @@
|
||||
from mailu import models
|
||||
from mailu.ui import ui, forms, access
|
||||
|
||||
from flask import current_app as app
|
||||
import flask
|
||||
import flask_login
|
||||
|
||||
@ -49,6 +50,9 @@ def announcement():
|
||||
flask.flash('Your announcement was sent', 'success')
|
||||
return flask.render_template('announcement.html', form=form)
|
||||
|
||||
@ui.route('/webmail', methods=['GET'])
|
||||
def webmail():
|
||||
return flask.redirect(app.config['WEB_WEBMAIL'])
|
||||
|
||||
@ui.route('/client', methods=['GET'])
|
||||
def client():
|
||||
|
@ -21,7 +21,10 @@ mail_access_groups = mail
|
||||
maildir_stat_dirs = yes
|
||||
mailbox_list_index = yes
|
||||
mail_vsize_bg_after_count = 100
|
||||
mail_plugins = $mail_plugins quota quota_clone zlib{{ ' ' }}
|
||||
mail_plugins = $mail_plugins quota quota_clone{{ ' ' }}
|
||||
{%- if COMPRESSION -%}
|
||||
zlib{{ ' ' }}
|
||||
{%- endif %}
|
||||
{%- if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] -%}
|
||||
fts fts_xapian
|
||||
{%- endif %}
|
||||
@ -50,7 +53,7 @@ plugin {
|
||||
fts_autoindex_exclude = \Trash
|
||||
{% endif %}
|
||||
|
||||
{% if COMPRESSION in [ 'gz', 'bz2' ] %}
|
||||
{% if COMPRESSION in [ 'gz', 'bz2', 'lz4', 'zstd' ] %}
|
||||
zlib_save = {{ COMPRESSION }}
|
||||
{% endif %}
|
||||
|
||||
|
@ -136,9 +136,33 @@ http {
|
||||
include /etc/nginx/proxy.conf;
|
||||
client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }};
|
||||
proxy_pass http://$webmail;
|
||||
{% if ADMIN == 'true' %}
|
||||
auth_request /internal/auth/user;
|
||||
error_page 403 @webmail_login;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
location {{ WEB_WEBMAIL }}/sso.php {
|
||||
{% if WEB_WEBMAIL != '/' %}
|
||||
rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent;
|
||||
rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break;
|
||||
{% endif %}
|
||||
include /etc/nginx/proxy.conf;
|
||||
client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }};
|
||||
auth_request /internal/auth/user;
|
||||
auth_request_set $user $upstream_http_x_user;
|
||||
auth_request_set $token $upstream_http_x_user_token;
|
||||
proxy_set_header X-Remote-User $user;
|
||||
proxy_set_header X-Remote-User-Token $token;
|
||||
proxy_pass http://$webmail;
|
||||
error_page 403 @webmail_login;
|
||||
}
|
||||
|
||||
location @webmail_login {
|
||||
return 302 {{ WEB_ADMIN }}/ui/login?next=ui.webmail;
|
||||
}
|
||||
{% else %}
|
||||
}
|
||||
{% endif %}{% endif %}
|
||||
{% if ADMIN == 'true' %}
|
||||
location {{ WEB_ADMIN }} {
|
||||
return 301 {{ WEB_ADMIN }}/ui;
|
||||
|
@ -10,7 +10,6 @@ log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
|
||||
|
||||
# Actual startup script
|
||||
|
||||
os.environ["FRONT_ADDRESS"] = system.get_host_address_from_environment("FRONT", "front")
|
||||
os.environ["REDIS_ADDRESS"] = system.get_host_address_from_environment("REDIS", "redis")
|
||||
|
||||
if os.environ.get("ANTIVIRUS") == 'clamav':
|
||||
|
@ -97,7 +97,7 @@ 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!
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
@ -195,4 +195,24 @@ resolved. This can be used to rely on DNS based service discovery with changing
|
||||
When using ``*_ADDRESS``, the hostnames must be full-qualified hostnames. Otherwise nginx will not be able to
|
||||
resolve the hostnames.
|
||||
|
||||
Database settings
|
||||
-----------------
|
||||
|
||||
|
||||
The admin service stores configurations in a database.
|
||||
|
||||
- ``DB_FLAVOR``: the database type for mailu admin service. (``sqlite``, ``postgresql``, ``mysql``)
|
||||
- ``DB_HOST``: the database host for mailu admin service. (when not ``sqlite``)
|
||||
- ``DB_PORT``: the database port for mailu admin service. (when not ``sqlite``)
|
||||
- ``DB_PW``: the database password for mailu admin service. (when not ``sqlite``)
|
||||
- ``DB_USER``: the database user for mailu admin service. (when not ``sqlite``)
|
||||
- ``DB_NAME``: the database name for mailu admin service. (when not ``sqlite``)
|
||||
|
||||
The roundcube service stores configurations in a database.
|
||||
|
||||
- ``ROUNDCUBE_DB_FLAVOR``: the database type for roundcube service. (``sqlite``, ``postgresql``, ``mysql``)
|
||||
- ``ROUNDCUBE_DB_HOST``: the database host for roundcube service. (when not ``sqlite``)
|
||||
- ``ROUNDCUBE_DB_PORT``: the database port for roundcube service. (when not ``sqlite``)
|
||||
- ``ROUNDCUBE_DB_PW``: the database password for roundcube service. (when not ``sqlite``)
|
||||
- ``ROUNDCUBE_DB_USER``: the database user for roundcube service. (when not ``sqlite``)
|
||||
- ``ROUNDCUBE_DB_NAME``: the database name for roundcube service. (when not ``sqlite``)
|
||||
|
@ -178,9 +178,9 @@ In the case of a PR from a fellow team member, a single review is enough
|
||||
to initiate merging. In all other cases, two approving reviews are required.
|
||||
There is also a possibility to set the ``review/need2`` to require a second review.
|
||||
|
||||
After Travis successfully tests the PR and the required amount of reviews are acquired,
|
||||
After the Github Action workflow successfully tests the PR and the required amount of reviews are acquired,
|
||||
Mergify will trigger with a ``bors r+`` command. Bors will batch any approved PR's,
|
||||
merges them with master in a staging branch where Travis builds and tests the result.
|
||||
merges them with master in a staging branch where the Github Action workflow builds and tests the result.
|
||||
After a successful test, the actual master gets fast-forwarded to that point.
|
||||
|
||||
System requirements
|
||||
@ -201,16 +201,16 @@ us on `Matrix`_.
|
||||
Test images
|
||||
```````````
|
||||
|
||||
All PR's automatically get build by Travis, controlled by `bors-ng`_.
|
||||
All PR's automatically get build by a Github Action workflow, controlled by `bors-ng`_.
|
||||
Some primitive auto testing is done.
|
||||
The resulting images get uploaded to Docker hub, under the
|
||||
tag name ``mailutest/<name>:pr-<no>``.
|
||||
tag name ``mailuci/<name>:pr-<no>``.
|
||||
|
||||
For example, to test PR #500 against master, reviewers can use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export DOCKER_ORG="mailutest"
|
||||
export DOCKER_ORG="mailuci"
|
||||
export MAILU_VERSION="pr-500"
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
@ -232,8 +232,8 @@ after Bors confirms a successful build.
|
||||
When bors try fails
|
||||
```````````````````
|
||||
|
||||
Sometimes Travis fails when another PR triggers a ``bors try`` command,
|
||||
before Travis cloned the git repository.
|
||||
Sometimes the Github Action workflow fails when another PR triggers a ``bors try`` command,
|
||||
before the Github Action workflow cloned the git repository.
|
||||
Inspect the build log in the link provided by *bors-ng* to find out the cause.
|
||||
If you see something like the following error on top of the logs,
|
||||
feel free to write a comment with ``bors retry``.
|
||||
|
@ -41,7 +41,7 @@ PR Workflow
|
||||
-----------
|
||||
|
||||
All pull requests have to be against the main ``master`` branch.
|
||||
The PR gets build by Travis and some primitive auto-testing is done.
|
||||
The PR gets build by a Github Action workflow and some primitive auto-testing is done.
|
||||
Test images get uploaded to a separate section in Docker hub.
|
||||
Reviewers will check the PR and test the resulting images.
|
||||
See the :ref:`testing` section for more info.
|
||||
|
@ -8,7 +8,8 @@ This functionality should still be considered experimental!
|
||||
Mailu Postgresql
|
||||
----------------
|
||||
|
||||
Mailu optionally comes with a pre-configured Postgresql image.
|
||||
Mailu optionally comes with a pre-configured Postgresql image, which as of 1.8, is deprecated
|
||||
and will be removed in 1.9.
|
||||
This images has the following features:
|
||||
|
||||
- Automatic creation of users, db, extensions and password;
|
||||
|
33
docs/faq.rst
33
docs/faq.rst
@ -61,7 +61,7 @@ have to prevent pushing out something quickly.
|
||||
We currently maintain a strict work flow:
|
||||
|
||||
#. Someone writes a solution and sends a pull request;
|
||||
#. We use Travis-CI for some very basic building and testing;
|
||||
#. We use Github actions for some very basic building and testing;
|
||||
#. The pull request needs to be code-reviewed and tested by at least two members
|
||||
from the contributors team.
|
||||
|
||||
@ -261,6 +261,8 @@ correct syntax. The following file names will be taken as override configuration
|
||||
- ``main.cf`` as ``$ROOT/overrides/postfix/postfix.cf``
|
||||
- ``master.cf`` as ``$ROOT/overrides/postfix/postfix.master``
|
||||
- All ``$ROOT/overrides/postfix/*.map`` files
|
||||
- For both ``postfix.cf`` and ``postfix.master``, you need to put one configuration per line, as they are fed line-by-line
|
||||
to postfix.
|
||||
- `Dovecot`_ - ``dovecot.conf`` in dovecot sub-directory;
|
||||
- `Nginx`_ - All ``*.conf`` files in the ``nginx`` sub-directory;
|
||||
- `Rspamd`_ - All files in the ``rspamd`` sub-directory.
|
||||
@ -528,25 +530,42 @@ The above will block flagged IPs for a week, you can of course change it to you
|
||||
|
||||
actionstart = iptables -N f2b-bad-auth
|
||||
iptables -A f2b-bad-auth -j RETURN
|
||||
iptables -I FORWARD -p tcp -m multiport --dports 1:1024 -j f2b-bad-auth
|
||||
iptables -I DOCKER-USER -p tcp -m multiport --dports 1:1024 -j f2b-bad-auth
|
||||
|
||||
actionstop = iptables -D FORWARD -p tcp -m multiport --dports 1:1024 -j f2b-bad-auth
|
||||
actionstop = iptables -D DOCKER-USER -p tcp -m multiport --dports 1:1024 -j f2b-bad-auth
|
||||
iptables -F f2b-bad-auth
|
||||
iptables -X f2b-bad-auth
|
||||
|
||||
actioncheck = iptables -n -L FORWARD | grep -q 'f2b-bad-auth[ \t]'
|
||||
actioncheck = iptables -n -L DOCKER-USER | grep -q 'f2b-bad-auth[ \t]'
|
||||
|
||||
actionban = iptables -I f2b-bad-auth 1 -s <ip> -j DROP
|
||||
|
||||
actionunban = iptables -D f2b-bad-auth -s <ip> -j DROP
|
||||
|
||||
5. Restart Fail2Ban
|
||||
Using DOCKER-USER chain ensures that the blocked IPs are processed in the correct order with Docker. See more in: https://docs.docker.com/network/iptables/
|
||||
|
||||
5. Configure and restart the Fail2Ban service
|
||||
|
||||
Make sure Fail2Ban is started after the Docker service by adding a partial override which appends this to the existing configuration.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo systemctl edit fail2ban
|
||||
|
||||
Add the override and save the file.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[Unit]
|
||||
After=docker.service
|
||||
|
||||
Restart the Fail2Ban service.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo systemctl restart fail2ban
|
||||
|
||||
*Issue reference:* `85`_, `116`_, `171`_, `584`_, `592`_.
|
||||
*Issue reference:* `85`_, `116`_, `171`_, `584`_, `592`_, `1727`_.
|
||||
|
||||
Users can't change their password from webmail
|
||||
``````````````````````````````````````````````
|
||||
@ -670,7 +689,7 @@ iptables -t nat -A POSTROUTING -o eth0 -p tcp --dport 25 -j SNAT --to <your mx i
|
||||
.. _`1090`: https://github.com/Mailu/Mailu/issues/1090
|
||||
.. _`unbound`: https://nlnetlabs.nl/projects/unbound/about/
|
||||
.. _`1438`: https://github.com/Mailu/Mailu/issues/1438
|
||||
|
||||
.. _`1727`: https://github.com/Mailu/Mailu/issues/1727
|
||||
|
||||
A user gets ``Sender address rejected: Access denied. Please check the`` ``message recipient […] and try again`` even though the sender is legitimate?
|
||||
``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
|
||||
|
@ -3,6 +3,10 @@
|
||||
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).
|
||||
|
||||
Prequisites
|
||||
-----------
|
||||
|
||||
|
@ -154,7 +154,40 @@ Add the respective Traefik labels for your domain/configuration, like
|
||||
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``,
|
||||
and this is the ``DOMAIN`` in your ``.env``?
|
||||
To support that use-case, Traefik can request ``SANs`` for your domain. Lets add something like
|
||||
To support that use-case, Traefik can request ``SANs`` for your domain. The configuration for this will depend on your Traefik version.
|
||||
|
||||
----
|
||||
|
||||
Traefik 2.x using labels configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Add the appropriate labels for your domain(s) to the ``front`` container in ``docker-compose.yml``.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
services:
|
||||
front:
|
||||
labels:
|
||||
# Enable TLS
|
||||
- "traefik.http.routers.mailu-secure.tls"
|
||||
# Your main domain
|
||||
- "traefik.http.routers.mailu-secure.tls.domains[0].main=your.doma.in"
|
||||
# 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"
|
||||
# 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"
|
||||
# Your ACME certificate resolver
|
||||
- "traefik.http.routers.mailu-secure.tls.certResolver=foo"
|
||||
|
||||
Of course, be sure to define the Certificate Resolver ``foo`` in the static configuration as well.
|
||||
|
||||
Alternatively, you can define SANs in the Traefik static configuration using routers, or in the static configuration using entrypoints. Refer to the Traefik documentation for more details.
|
||||
|
||||
Traefik 1.x with TOML configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lets add something like
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -163,7 +196,11 @@ To support that use-case, Traefik can request ``SANs`` for your domain. Lets add
|
||||
main = "your.doma.in" # this is the same as $TRAEFIK_DOMAIN!
|
||||
sans = ["mail.your.doma.in", "webmail.your.doma.in", "smtp.your.doma.in"]
|
||||
|
||||
to your ``traefik.toml``. You might need to clear your ``acme.json``, if a certificate for one of these domains already exists.
|
||||
to your ``traefik.toml``.
|
||||
|
||||
----
|
||||
|
||||
You might need to clear your ``acme.json``, if a certificate for one of these domains already exists.
|
||||
|
||||
You will need some solution which dumps the certificates in ``acme.json``, so you can include them in the ``mailu/front`` container.
|
||||
One such example is ``mailu/traefik-certdumper``, which has been adapted for use in Mailu. You can add it to your ``docker-compose.yml`` like:
|
||||
|
@ -215,22 +215,29 @@ On the new relayed domain page the following options can be entered for a new re
|
||||
* Relayed domain name. The domain name that is relayed. Email messages addressed to this domain (To: John@example.com), will be forwarded to this domain.
|
||||
No authentication is required.
|
||||
|
||||
* Remote host (optional). The SMPT server that will be used for relaying the email message.
|
||||
When this field is blank, the Mailu server will directly send the email message to the relayed domain.
|
||||
As value can be entered either a hostname or IP address of the SMPT server.
|
||||
By default port 25 is used. To use a different port append ":port number" to the Remote Host. For example:
|
||||
123.45.67.90:2525.
|
||||
* Remote host (optional). The host that will be used for relaying the email message.
|
||||
When this field is blank, the Mailu server will directly send the email message to the mail server of the relayed domain.
|
||||
When a remote host is specified it can be prefixed by ``mx:`` or ``lmtp:`` and followed by a port number: ``:port``).
|
||||
|
||||
================ ===================================== =========================
|
||||
Remote host Description postfix transport:nexthop
|
||||
================ ===================================== =========================
|
||||
empty use MX of relay domain smtp:domain
|
||||
:port use MX of relay domain and use port smtp:domain:port
|
||||
target resolve A/AAAA of target smtp:[target]
|
||||
target:port resolve A/AAAA of target and use port smtp:[target]:port
|
||||
mx:target resolve MX of target smtp:target
|
||||
mx:target:port resolve MX of target and use port smtp:target:port
|
||||
lmtp:target resolve A/AAAA of target lmtp:target
|
||||
lmtp:target:port resolve A/AAAA of target and use port lmtp:target:port
|
||||
================ ===================================== =========================
|
||||
|
||||
`target` can also be an IPv4 or IPv6 address (an IPv6 address must be enclosed in []: ``[2001:DB8::]``).
|
||||
|
||||
* Comment. A text field where a comment can be entered to describe the entry.
|
||||
|
||||
Changes are effective immediately after clicking the Save button.
|
||||
|
||||
NOTE: Due to bug `1588`_ email messages fail to be relayed if no Remote Host is configured.
|
||||
As a workaround the HOSTNAME or IP Address of the SMPT server of the relayed domain can be entered as Remote Host.
|
||||
Please note that no MX lookup is performed when entering a hostname as Remote Host. You can use the MX lookup on mxtoolbox.com to find the hostname and IP Address of the SMTP server.
|
||||
|
||||
.. _`1588`: https://github.com/Mailu/Mailu/issues/1588
|
||||
|
||||
Antispam
|
||||
--------
|
||||
|
||||
|
@ -3,6 +3,7 @@ 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
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import anosql
|
||||
import psycopg2
|
||||
import jinja2
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
@ -38,7 +37,6 @@ if not os.listdir("/data"):
|
||||
rec.write("restore_command = 'gunzip < /backup/wal_archive/%f > %p'\n")
|
||||
rec.write("standby_mode = off\n")
|
||||
os.system("chown postgres:postgres /data/recovery.conf")
|
||||
#os.system("sudo -u postgres pg_ctl start -D /data -o '-h \"''\" '")
|
||||
else:
|
||||
# Bootstrap the database
|
||||
os.system("sudo -u postgres initdb -D /data")
|
||||
|
@ -2,7 +2,7 @@ server:
|
||||
verbosity: 1
|
||||
interface: 0.0.0.0
|
||||
interface: ::0
|
||||
logfile: /dev/stdout
|
||||
logfile: ""
|
||||
do-ip4: yes
|
||||
do-ip6: yes
|
||||
do-udp: yes
|
||||
|
@ -26,7 +26,7 @@ services:
|
||||
{% if bind4 %}
|
||||
- "{{ bind4 }}:{{ port }}:{{ port }}"
|
||||
{% endif %}
|
||||
{% if bind6 %}
|
||||
{% if ipv6_enabled and bind6 %}
|
||||
- "{{ bind6 }}:{{ port }}:{{ port }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@ -85,6 +85,7 @@ services:
|
||||
|
||||
antispam:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-{{ version }}}
|
||||
hostname: antispam
|
||||
restart: always
|
||||
env_file: {{ env }}
|
||||
volumes:
|
||||
|
@ -86,7 +86,7 @@ WELCOME_SUBJECT={{ welcome_subject or 'Welcome to your new email account' }}
|
||||
WELCOME_BODY={{ welcome_body or 'Welcome to your new email account, if you can read this, then it is configured properly!' }}
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION={{ compression }}
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL={{ compression_level }}
|
||||
@ -175,3 +175,10 @@ DB_HOST={{ db_url }}
|
||||
DB_NAME={{ db_name }}
|
||||
{% endif %}
|
||||
|
||||
{% if (postgresql == 'external' or db_flavor == 'mysql') and webmail_type == 'roundcube' %}
|
||||
ROUNDCUBE_DB_FLAVOR={{ db_flavor }}
|
||||
ROUNDCUBE_DB_USER={{ roundcube_db_user }}
|
||||
ROUNDCUBE_DB_PW={{ roundcube_db_pw }}
|
||||
ROUNDCUBE_DB_HOST={{ roundcube_db_url }}
|
||||
ROUNDCUBE_DB_NAME={{ roundcube_db_name }}
|
||||
{% endif %}
|
||||
|
@ -70,6 +70,7 @@ services:
|
||||
|
||||
antispam:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-{{ version }}}
|
||||
hostname: antispam
|
||||
env_file: {{ env }}
|
||||
volumes:
|
||||
- "{{ root }}/filter:/var/lib/rspamd"
|
||||
|
@ -57,6 +57,13 @@ $(document).ready(function() {
|
||||
$("#db_pw").prop('required',true);
|
||||
$("#db_url").prop('required',true);
|
||||
$("#db_name").prop('required',true);
|
||||
if ($("#webmail").val() == 'roundcube') {
|
||||
$("#roundcube_external_db").show();
|
||||
$("#roundcube_db_user").prop('required',true);
|
||||
$("#roundcube_db_pw").prop('required',true);
|
||||
$("#roundcube_db_url").prop('required',true);
|
||||
$("#roundcube_db_name").prop('required',true);
|
||||
}
|
||||
} else if (this.value == 'mysql') {
|
||||
$("#postgres_db").hide();
|
||||
$("#external_db").show();
|
||||
@ -64,6 +71,13 @@ $(document).ready(function() {
|
||||
$("#db_pw").prop('required',true);
|
||||
$("#db_url").prop('required',true);
|
||||
$("#db_name").prop('required',true);
|
||||
if ($("#webmail").val() == 'roundcube') {
|
||||
$("#roundcube_external_db").show();
|
||||
$("#roundcube_db_user").prop('required',true);
|
||||
$("#roundcube_db_pw").prop('required',true);
|
||||
$("#roundcube_db_url").prop('required',true);
|
||||
$("#roundcube_db_name").prop('required',true);
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#external_psql").change(function() {
|
||||
@ -73,6 +87,13 @@ $(document).ready(function() {
|
||||
$("#db_pw").prop('required',true);
|
||||
$("#db_url").prop('required',true);
|
||||
$("#db_name").prop('required',true);
|
||||
if ($("#webmail").val() == 'roundcube') {
|
||||
$("#roundcube_external_db").show();
|
||||
$("#roundcube_db_user").prop('required',true);
|
||||
$("#roundcube_db_pw").prop('required',true);
|
||||
$("#roundcube_db_url").prop('required',true);
|
||||
$("#roundcube_db_name").prop('required',true);
|
||||
}
|
||||
} else {
|
||||
$("#external_db").hide();
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
<br/>
|
||||
</div>
|
||||
<div class="form-group" id="external_db" style="display: none">
|
||||
<p>Set external database parameters</p>
|
||||
<p>Set external database parameters for <b>Admin UI</b></p>
|
||||
<label>DB User</label>
|
||||
<input class="form-control" type="text" name="db_user" placeholder="Username" id="db_user">
|
||||
<label>Db Password</label>
|
||||
@ -37,6 +37,18 @@
|
||||
<input class="form-control" type="text" name="db_url" placeholder="URL" id="db_url">
|
||||
<label>Db Name</label>
|
||||
<input class="form-control" type="text" name="db_name" placeholder="Database Name" id="db_name">
|
||||
<br/>
|
||||
<div class="form-group" id="roundcube_external_db" style="display: none">
|
||||
<p>Set external database parameters for <b>Roundcube</b></p>
|
||||
<label>DB User</label>
|
||||
<input class="form-control" type="text" name="roundcube_db_user" placeholder="Username" id="roundcube_db_user">
|
||||
<label>DB Password</label>
|
||||
<input class="form-control" type="password" name="roundcube_db_pw" placeholder="Password" id="roundcube_db_pw">
|
||||
<label>DB URL</label>
|
||||
<input class="form-control" type="text" name="roundcube_db_url" placeholder="URL" id="roundcube_db_url">
|
||||
<label>DB Name</label>
|
||||
<input class="form-control" type="text" name="roundcube_db_name" placeholder="Database Name" id="roundcube_db_name">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -92,7 +92,7 @@ DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
@ -92,7 +92,7 @@ DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
5
tests/compose/filters/00_create_users.sh
Executable file
5
tests/compose/filters/00_create_users.sh
Executable file
@ -0,0 +1,5 @@
|
||||
echo "Creating user required for next test ..."
|
||||
# Should not fail and update the password; update mode
|
||||
docker-compose -f tests/compose/filters/docker-compose.yml exec -T admin flask mailu admin admin mailu.io 'password' --mode=update || exit 1
|
||||
docker-compose -f tests/compose/filters/docker-compose.yml exec -T admin flask mailu user user mailu.io 'password' || exit 1
|
||||
echo "User created successfully"
|
@ -92,7 +92,7 @@ DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
@ -51,7 +51,7 @@ DISABLE_STATISTICS=False
|
||||
###################################
|
||||
|
||||
# Expose the admin interface (value: true, false)
|
||||
ADMIN=true
|
||||
ADMIN=false
|
||||
|
||||
# Choose which webmail to run if any (values: roundcube, rainloop, none)
|
||||
WEBMAIL=rainloop
|
||||
@ -92,7 +92,7 @@ DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
@ -51,7 +51,7 @@ DISABLE_STATISTICS=False
|
||||
###################################
|
||||
|
||||
# Expose the admin interface (value: true, false)
|
||||
ADMIN=true
|
||||
ADMIN=false
|
||||
|
||||
# Choose which webmail to run if any (values: roundcube, rainloop, none)
|
||||
WEBMAIL=roundcube
|
||||
@ -92,7 +92,7 @@ DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
@ -92,7 +92,7 @@ DMARC_RUF=admin
|
||||
|
||||
|
||||
# Maildir Compression
|
||||
# choose compression-method, default: none (value: bz2, gz)
|
||||
# choose compression-method, default: none (value: gz, bz2, lz4, zstd)
|
||||
COMPRESSION=
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL=
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
# Retag in case of `bors try`
|
||||
if [ "$TRAVIS_BRANCH" = "testing" ]; then
|
||||
export DOCKER_ORG="mailutest"
|
||||
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]/}"
|
||||
|
1
towncrier/newsfragments/1194.feature
Normal file
1
towncrier/newsfragments/1194.feature
Normal file
@ -0,0 +1 @@
|
||||
Add a credential cache to speedup authentication requests.
|
1
towncrier/newsfragments/1503.doc
Normal file
1
towncrier/newsfragments/1503.doc
Normal file
@ -0,0 +1 @@
|
||||
Add documentation for Traefik 2 in Reverse Proxy
|
1
towncrier/newsfragments/1694.feature
Normal file
1
towncrier/newsfragments/1694.feature
Normal file
@ -0,0 +1 @@
|
||||
Support configuring lz4 and zstd compression for dovecot.
|
2
towncrier/newsfragments/1760.bugfix
Normal file
2
towncrier/newsfragments/1760.bugfix
Normal file
@ -0,0 +1,2 @@
|
||||
Fix CVE-2021-23240, CVE-2021-3156 and CVE-2021-23239 for postgresql
|
||||
by force-upgrading sudo.
|
1
towncrier/newsfragments/1828.misc
Normal file
1
towncrier/newsfragments/1828.misc
Normal file
@ -0,0 +1 @@
|
||||
Switched from Travis to Github actions for CI/CD. Improved CI workflow to perform all tests in parallel.
|
1
towncrier/newsfragments/1830.misc
Normal file
1
towncrier/newsfragments/1830.misc
Normal file
@ -0,0 +1 @@
|
||||
Make CI tests run in parallel.
|
1
towncrier/newsfragments/1831.bugfix
Normal file
1
towncrier/newsfragments/1831.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix roundcube environment configuration for databases
|
1
towncrier/newsfragments/1837.bugfix
Normal file
1
towncrier/newsfragments/1837.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Antispam service now uses a static hostname. Rspamd history is only retained when the service has a fixed hostname.
|
1
towncrier/newsfragments/224.feature
Normal file
1
towncrier/newsfragments/224.feature
Normal file
@ -0,0 +1 @@
|
||||
Add instructions on how to create DNS records for email client auto-configuration (RFC6186 style)
|
1
towncrier/newsfragments/783.feature
Normal file
1
towncrier/newsfragments/783.feature
Normal file
@ -0,0 +1 @@
|
||||
Centralize the authentication of webmails behind the admin interface
|
@ -35,6 +35,7 @@ RUN apt-get update && apt-get install -y \
|
||||
&& 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
|
||||
|
@ -8,6 +8,10 @@ allow_admin_panel = Off
|
||||
|
||||
[labs]
|
||||
allow_gravatar = Off
|
||||
{% if ADMIN == "true" %}
|
||||
custom_login_link='sso.php'
|
||||
custom_logout_link='{{ WEB_ADMIN }}/ui/logout'
|
||||
{% endif %}
|
||||
|
||||
[contacts]
|
||||
enable = On
|
||||
|
31
webmails/rainloop/sso.php
Normal file
31
webmails/rainloop/sso.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
$_ENV['RAINLOOP_INCLUDE_AS_API'] = true;
|
||||
if (!defined('APP_VERSION')) {
|
||||
$version = file_get_contents('/data/VERSION');
|
||||
if ($version) {
|
||||
define('APP_VERSION', $version);
|
||||
define('APP_INDEX_ROOT_FILE', __FILE__);
|
||||
define('APP_INDEX_ROOT_PATH', str_replace('\\', '/', rtrim(dirname(__FILE__), '\\/').'/'));
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists(APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/include.php')) {
|
||||
include APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/include.php';
|
||||
} else {
|
||||
echo '[105] Missing version directory';
|
||||
exit(105);
|
||||
}
|
||||
|
||||
// Retrieve email and password
|
||||
if (in_array('HTTP_X_REMOTE_USER', $_SERVER) && in_array('HTTP_X_REMOTE_USER_TOKEN', $_SERVER)) {
|
||||
$email = $_SERVER['HTTP_X_REMOTE_USER'];
|
||||
$password = $_SERVER['HTTP_X_REMOTE_USER_TOKEN'];
|
||||
$ssoHash = \RainLoop\Api::GetUserSsoHash($email, $password);
|
||||
|
||||
// redirect to webmail sso url
|
||||
header('Location: index.php?sso&hash='.$ssoHash);
|
||||
}
|
||||
else {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
}
|
@ -24,6 +24,7 @@ conf.jinja("/application.ini", os.environ, "/data/_data_/_default_/configs/appli
|
||||
conf.jinja("/php.ini", os.environ, "/usr/local/etc/php/conf.d/rainloop.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"])
|
||||
|
||||
|
@ -46,6 +46,7 @@ RUN apt-get update && apt-get install -y \
|
||||
|
||||
COPY php.ini /php.ini
|
||||
COPY config.inc.php /var/www/html/config/
|
||||
COPY mailu.php /var/www/html/plugins/mailu/mailu.php
|
||||
COPY start.py /start.py
|
||||
|
||||
EXPOSE 80/tcp
|
||||
|
@ -36,7 +36,11 @@ $config['managesieve_host'] = $imap;
|
||||
$config['managesieve_usetls'] = false;
|
||||
|
||||
// Customization settings
|
||||
$config['support_url'] = getenv('WEB_ADMIN') ? '../..' . getenv('WEB_ADMIN') : '';
|
||||
if (filter_var(getenv('ADMIN'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) {
|
||||
array_push($config['plugins'], 'mailu');
|
||||
$config['support_url'] = getenv('WEB_ADMIN') ? '../..' . getenv('WEB_ADMIN') : '';
|
||||
$config['sso_logout_url'] = getenv('WEB_ADMIN').'/ui/logout';
|
||||
}
|
||||
$config['product_name'] = 'Mailu Webmail';
|
||||
|
||||
// We access the IMAP and SMTP servers locally with internal names, SSL
|
||||
|
59
webmails/roundcube/mailu.php
Normal file
59
webmails/roundcube/mailu.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
class mailu extends rcube_plugin
|
||||
{
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->add_hook('startup', array($this, 'startup'));
|
||||
$this->add_hook('authenticate', array($this, 'authenticate'));
|
||||
$this->add_hook('login_after', array($this, 'login'));
|
||||
$this->add_hook('login_failed', array($this, 'login_failed'));
|
||||
$this->add_hook('logout_after', array($this, 'logout'));
|
||||
}
|
||||
|
||||
function startup($args)
|
||||
{
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
$args['action'] = 'login';
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
function authenticate($args)
|
||||
{
|
||||
if (!in_array('HTTP_X_REMOTE_USER', $_SERVER) || !in_array('HTTP_X_REMOTE_USER_TOKEN', $_SERVER)) {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
die();
|
||||
}
|
||||
$args['user'] = $_SERVER['HTTP_X_REMOTE_USER'];
|
||||
$args['pass'] = $_SERVER['HTTP_X_REMOTE_USER_TOKEN'];
|
||||
|
||||
$args['cookiecheck'] = false;
|
||||
$args['valid'] = true;
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
function logout($args) {
|
||||
// Redirect to global SSO logout path.
|
||||
$this->load_config();
|
||||
|
||||
$sso_logout_url = rcmail::get_instance()->config->get('sso_logout_url');
|
||||
header("Location: " . $sso_logout_url, true);
|
||||
exit;
|
||||
}
|
||||
|
||||
function login($args)
|
||||
{
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
function login_failed($args)
|
||||
{
|
||||
header('Location: sso.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
}
|
@ -8,41 +8,42 @@ import subprocess
|
||||
|
||||
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
|
||||
|
||||
os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT"))*0.66/1048576))
|
||||
os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT")) * 0.66 / 1048576))
|
||||
|
||||
db_flavor=os.environ.get("ROUNDCUBE_DB_FLAVOR",os.environ.get("DB_FLAVOR","sqlite"))
|
||||
if db_flavor=="sqlite":
|
||||
os.environ["DB_DSNW"]="sqlite:////data/roundcube.db"
|
||||
elif db_flavor=="mysql":
|
||||
os.environ["DB_DSNW"]="mysql://%s:%s@%s/%s" % (
|
||||
os.environ.get("ROUNDCUBE_DB_USER","roundcube"),
|
||||
db_flavor = os.environ.get("ROUNDCUBE_DB_FLAVOR", "sqlite")
|
||||
if db_flavor == "sqlite":
|
||||
os.environ["DB_DSNW"] = "sqlite:////data/roundcube.db"
|
||||
elif db_flavor == "mysql":
|
||||
os.environ["DB_DSNW"] = "mysql://%s:%s@%s/%s" % (
|
||||
os.environ.get("ROUNDCUBE_DB_USER", "roundcube"),
|
||||
os.environ.get("ROUNDCUBE_DB_PW"),
|
||||
os.environ.get("ROUNDCUBE_DB_HOST",os.environ.get("DB_HOST","database")),
|
||||
os.environ.get("ROUNDCUBE_DB_NAME","roundcube")
|
||||
os.environ.get("ROUNDCUBE_DB_HOST", "database"),
|
||||
os.environ.get("ROUNDCUBE_DB_NAME", "roundcube")
|
||||
)
|
||||
elif db_flavor=="postgresql":
|
||||
os.environ["DB_DSNW"]="pgsql://%s:%s@%s/%s" % (
|
||||
os.environ.get("ROUNDCUBE_DB_USER","roundcube"),
|
||||
elif db_flavor == "postgresql":
|
||||
os.environ["DB_DSNW"] = "pgsql://%s:%s@%s/%s" % (
|
||||
os.environ.get("ROUNDCUBE_DB_USER", "roundcube"),
|
||||
os.environ.get("ROUNDCUBE_DB_PW"),
|
||||
os.environ.get("ROUNDCUBE_DB_HOST",os.environ.get("DB_HOST","database")),
|
||||
os.environ.get("ROUNDCUBE_DB_NAME","roundcube")
|
||||
os.environ.get("ROUNDCUBE_DB_HOST", "database"),
|
||||
os.environ.get("ROUNDCUBE_DB_NAME", "roundcube")
|
||||
)
|
||||
else:
|
||||
print("Unknown ROUNDCUBE_DB_FLAVOR: %s",db_flavor)
|
||||
print("Unknown ROUNDCUBE_DB_FLAVOR: %s", db_flavor)
|
||||
exit(1)
|
||||
|
||||
|
||||
|
||||
conf.jinja("/php.ini", os.environ, "/usr/local/etc/php/conf.d/roundcube.ini")
|
||||
|
||||
# Create dirs, setup permissions
|
||||
os.system("mkdir -p /data/gpg /var/www/html/logs")
|
||||
os.system("touch /var/www/html/logs/errors.log")
|
||||
os.system("chown -R www-data:www-data /var/www/html/logs")
|
||||
os.system("chmod -R a+rX /var/www/html/")
|
||||
os.system("ln -sf /var/www/html/index.php /var/www/html/sso.php")
|
||||
|
||||
try:
|
||||
print("Initializing database")
|
||||
result=subprocess.check_output(["/var/www/html/bin/initdb.sh","--dir","/var/www/html/SQL"],stderr=subprocess.STDOUT)
|
||||
result = subprocess.check_output(["/var/www/html/bin/initdb.sh", "--dir", "/var/www/html/SQL"],
|
||||
stderr=subprocess.STDOUT)
|
||||
print(result.decode())
|
||||
except subprocess.CalledProcessError as e:
|
||||
if "already exists" in e.stdout.decode():
|
||||
@ -53,7 +54,7 @@ except subprocess.CalledProcessError as e:
|
||||
|
||||
try:
|
||||
print("Upgrading database")
|
||||
subprocess.check_call(["/var/www/html/bin/update.sh","--version=?","-y"],stderr=subprocess.STDOUT)
|
||||
subprocess.check_call(["/var/www/html/bin/update.sh", "--version=?", "-y"], stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
quit(1)
|
||||
|
||||
@ -61,7 +62,7 @@ except subprocess.CalledProcessError as e:
|
||||
os.system("chown -R www-data:www-data /data")
|
||||
|
||||
# Tail roundcube logs
|
||||
subprocess.Popen(["tail","-f","-n","0","/var/www/html/logs/errors.log"])
|
||||
subprocess.Popen(["tail", "-f", "-n", "0", "/var/www/html/logs/errors.log"])
|
||||
|
||||
# Run apache
|
||||
os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"])
|
||||
|
Loading…
x
Reference in New Issue
Block a user