You've already forked Mailu
mirror of
https://github.com/Mailu/Mailu.git
synced 2025-07-17 01:32:29 +02:00
Merge remote-tracking branch 'upstream/master' into upgrade-alpine
This commit is contained in:
49
.github/workflows/CI.yml
vendored
49
.github/workflows/CI.yml
vendored
@ -13,7 +13,7 @@ on:
|
|||||||
- '[1-9].[0-9].[0-9]'
|
- '[1-9].[0-9].[0-9]'
|
||||||
# pre-releases, e.g. 1.8-pre1
|
# pre-releases, e.g. 1.8-pre1
|
||||||
- 1.8-pre[0-9]
|
- 1.8-pre[0-9]
|
||||||
# test branches, e.g. test-debian
|
# test branches, e.g. test-debian
|
||||||
- test-*
|
- test-*
|
||||||
|
|
||||||
###############################################
|
###############################################
|
||||||
@ -39,6 +39,21 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
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
|
- name: Create folder for storing images
|
||||||
run: |
|
run: |
|
||||||
sudo mkdir -p /images
|
sudo mkdir -p /images
|
||||||
@ -58,7 +73,7 @@ jobs:
|
|||||||
run: echo "$DOCKER_PW" | docker login --username $DOCKER_UN --password-stdin
|
run: echo "$DOCKER_PW" | docker login --username $DOCKER_UN --password-stdin
|
||||||
- name: Build all docker images
|
- name: Build all docker images
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
run: docker-compose -f tests/build.yml build
|
run: docker-compose -f tests/build.yml build
|
||||||
@ -94,7 +109,7 @@ jobs:
|
|||||||
- name: Test core suite
|
- name: Test core suite
|
||||||
run: python tests/compose/test.py core 1
|
run: python tests/compose/test.py core 1
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
|
|
||||||
@ -127,7 +142,7 @@ jobs:
|
|||||||
- name: Test fetch
|
- name: Test fetch
|
||||||
run: python tests/compose/test.py fetchmail 1
|
run: python tests/compose/test.py fetchmail 1
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
|
|
||||||
@ -160,7 +175,7 @@ jobs:
|
|||||||
- name: Test clamvav
|
- name: Test clamvav
|
||||||
run: python tests/compose/test.py filters 2
|
run: python tests/compose/test.py filters 2
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
|
|
||||||
@ -193,7 +208,7 @@ jobs:
|
|||||||
- name: Test rainloop
|
- name: Test rainloop
|
||||||
run: python tests/compose/test.py rainloop 1
|
run: python tests/compose/test.py rainloop 1
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
|
|
||||||
@ -226,7 +241,7 @@ jobs:
|
|||||||
- name: Test roundcube
|
- name: Test roundcube
|
||||||
run: python tests/compose/test.py roundcube 1
|
run: python tests/compose/test.py roundcube 1
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
|
|
||||||
@ -259,7 +274,7 @@ jobs:
|
|||||||
- name: Test webdav
|
- name: Test webdav
|
||||||
run: python tests/compose/test.py webdav 1
|
run: python tests/compose/test.py webdav 1
|
||||||
env:
|
env:
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
|
|
||||||
@ -280,6 +295,21 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
|
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
|
- name: Create folder for storing images
|
||||||
run: |
|
run: |
|
||||||
sudo mkdir -p /images
|
sudo mkdir -p /images
|
||||||
@ -300,9 +330,8 @@ jobs:
|
|||||||
DOCKER_PW: ${{ secrets.Docker_Password }}
|
DOCKER_PW: ${{ secrets.Docker_Password }}
|
||||||
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
DOCKER_ORG: ${{ secrets.DOCKER_ORG }}
|
||||||
DOCKER_ORG_TESTS: ${{ secrets.DOCKER_ORG_TESTS }}
|
DOCKER_ORG_TESTS: ${{ secrets.DOCKER_ORG_TESTS }}
|
||||||
MAILU_VERSION: ${{ env.BRANCH }}
|
MAILU_VERSION: ${{ env.MAILU_VERSION }}
|
||||||
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
TRAVIS_BRANCH: ${{ env.BRANCH }}
|
||||||
TRAVIS_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
|
||||||
run: bash tests/deploy.sh
|
run: bash tests/deploy.sh
|
||||||
|
|
||||||
# This job is watched by bors. It only complets if building,testing and deploy worked.
|
# This job is watched by bors. It only complets if building,testing and deploy worked.
|
||||||
|
@ -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.
|
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
|
- [ ] 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.
|
||||||
|
@ -26,7 +26,7 @@ WORKDIR /app
|
|||||||
COPY requirements-prod.txt requirements.txt
|
COPY requirements-prod.txt requirements.txt
|
||||||
RUN apk add --no-cache openssl 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 \
|
&& apk add --no-cache --virtual build-dep \
|
||||||
openssl-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 \
|
&& pip3 install -r requirements.txt \
|
||||||
&& apk del --no-cache build-dep
|
&& apk del --no-cache build-dep
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ def basic_authentication():
|
|||||||
authorization = flask.request.headers.get("Authorization")
|
authorization = flask.request.headers.get("Authorization")
|
||||||
if authorization and authorization.startswith("Basic "):
|
if authorization and authorization.startswith("Basic "):
|
||||||
encoded = authorization.replace("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"))
|
user = models.User.query.get(user_email.decode("utf8"))
|
||||||
if nginx.check_credentials(user, password.decode('utf-8'), flask.request.remote_addr, "web"):
|
if nginx.check_credentials(user, password.decode('utf-8'), flask.request.remote_addr, "web"):
|
||||||
response = flask.Response()
|
response = flask.Response()
|
||||||
|
@ -57,10 +57,9 @@ class IdnaEmail(db.TypeDecorator):
|
|||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
""" encode unicode domain part of email address to punycode """
|
""" 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:
|
if '@' in localpart:
|
||||||
raise ValueError('email local part must not contain "@"')
|
raise ValueError('email local part must not contain "@"')
|
||||||
domain_name = domain_name.lower()
|
|
||||||
return f'{localpart}@{idna.encode(domain_name).decode("ascii")}'
|
return f'{localpart}@{idna.encode(domain_name).decode("ascii")}'
|
||||||
|
|
||||||
def process_result_value(self, value, dialect):
|
def process_result_value(self, value, dialect):
|
||||||
@ -272,11 +271,12 @@ class Domain(Base):
|
|||||||
return dkim.strip_key(dkim_key).decode('utf8')
|
return dkim.strip_key(dkim_key).decode('utf8')
|
||||||
|
|
||||||
def generate_dkim_key(self):
|
def generate_dkim_key(self):
|
||||||
""" generate and activate new DKIM key """
|
""" generate new DKIM key """
|
||||||
self.dkim_key = dkim.gen_key()
|
self.dkim_key = dkim.gen_key()
|
||||||
|
|
||||||
def has_email(self, localpart):
|
def has_email(self, localpart):
|
||||||
""" checks if localpart is configured for domain """
|
""" checks if localpart is configured for domain """
|
||||||
|
localpart = localpart.lower()
|
||||||
for email in chain(self.users, self.aliases):
|
for email in chain(self.users, self.aliases):
|
||||||
if email.localpart == localpart:
|
if email.localpart == localpart:
|
||||||
return True
|
return True
|
||||||
@ -355,8 +355,8 @@ class Email(object):
|
|||||||
@email.setter
|
@email.setter
|
||||||
def email(self, value):
|
def email(self, value):
|
||||||
""" setter for email - sets _email, localpart and domain_name at once """
|
""" setter for email - sets _email, localpart and domain_name at once """
|
||||||
self.localpart, self.domain_name = value.rsplit('@', 1)
|
self._email = value.lower()
|
||||||
self._email = value
|
self.localpart, self.domain_name = self._email.rsplit('@', 1)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _update_localpart(target, value, *_):
|
def _update_localpart(target, value, *_):
|
||||||
@ -371,8 +371,8 @@ class Email(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def __declare_last__(cls):
|
def __declare_last__(cls):
|
||||||
# gets called after mappings are completed
|
# gets called after mappings are completed
|
||||||
sqlalchemy.event.listen(User.localpart, 'set', cls._update_localpart, propagate=True)
|
sqlalchemy.event.listen(cls.localpart, 'set', cls._update_localpart, propagate=True)
|
||||||
sqlalchemy.event.listen(User.domain_name, 'set', cls._update_domain_name, propagate=True)
|
sqlalchemy.event.listen(cls.domain_name, 'set', cls._update_domain_name, propagate=True)
|
||||||
|
|
||||||
def sendmail(self, subject, body):
|
def sendmail(self, subject, body):
|
||||||
""" send an email to the address """
|
""" send an email to the address """
|
||||||
@ -389,8 +389,7 @@ class Email(object):
|
|||||||
def resolve_domain(cls, email):
|
def resolve_domain(cls, email):
|
||||||
""" resolves domain alternative to real domain """
|
""" resolves domain alternative to real domain """
|
||||||
localpart, domain_name = email.rsplit('@', 1) if '@' in email else (None, email)
|
localpart, domain_name = email.rsplit('@', 1) if '@' in email else (None, email)
|
||||||
alternative = Alternative.query.get(domain_name)
|
if alternative := Alternative.query.get(domain_name):
|
||||||
if alternative:
|
|
||||||
domain_name = alternative.domain_name
|
domain_name = alternative.domain_name
|
||||||
return (localpart, domain_name)
|
return (localpart, domain_name)
|
||||||
|
|
||||||
@ -401,12 +400,14 @@ class Email(object):
|
|||||||
localpart_stripped = None
|
localpart_stripped = None
|
||||||
stripped_alias = None
|
stripped_alias = None
|
||||||
|
|
||||||
if os.environ.get('RECIPIENT_DELIMITER') in localpart:
|
delim = os.environ.get('RECIPIENT_DELIMITER')
|
||||||
localpart_stripped = localpart.rsplit(os.environ.get('RECIPIENT_DELIMITER'), 1)[0]
|
if delim in localpart:
|
||||||
|
localpart_stripped = localpart.rsplit(delim, 1)[0]
|
||||||
|
|
||||||
user = User.query.get(f'{localpart}@{domain_name}')
|
user = User.query.get(f'{localpart}@{domain_name}')
|
||||||
if not user and localpart_stripped:
|
if not user and localpart_stripped:
|
||||||
user = User.query.get(f'{localpart_stripped}@{domain_name}')
|
user = User.query.get(f'{localpart_stripped}@{domain_name}')
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
email = f'{localpart}@{domain_name}'
|
email = f'{localpart}@{domain_name}'
|
||||||
|
|
||||||
@ -416,15 +417,15 @@ class Email(object):
|
|||||||
destination.append(email)
|
destination.append(email)
|
||||||
else:
|
else:
|
||||||
destination = [email]
|
destination = [email]
|
||||||
|
|
||||||
return destination
|
return destination
|
||||||
|
|
||||||
pure_alias = Alias.resolve(localpart, domain_name)
|
pure_alias = Alias.resolve(localpart, domain_name)
|
||||||
stripped_alias = Alias.resolve(localpart_stripped, domain_name)
|
|
||||||
|
|
||||||
if pure_alias and not pure_alias.wildcard:
|
if pure_alias and not pure_alias.wildcard:
|
||||||
return pure_alias.destination
|
return pure_alias.destination
|
||||||
|
|
||||||
if stripped_alias:
|
if stripped_alias := Alias.resolve(localpart_stripped, domain_name):
|
||||||
return stripped_alias.destination
|
return stripped_alias.destination
|
||||||
|
|
||||||
if pure_alias:
|
if pure_alias:
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.reply_enabled,
|
{{ macros.form_field(form.reply_enabled,
|
||||||
onchange="if(this.checked){$('#reply_subject,#reply_body,#reply_enddate,#reply_startdate').removeAttr('readonly')}
|
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,
|
{{ macros.form_field(form.reply_subject,
|
||||||
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
**{("rw" if user.reply_enabled else "readonly"): ""}) }}
|
||||||
{{ macros.form_field(form.reply_body, rows=10,
|
{{ macros.form_field(form.reply_body, rows=10,
|
||||||
|
@ -74,6 +74,8 @@ def domain_details(domain_name):
|
|||||||
def domain_genkeys(domain_name):
|
def domain_genkeys(domain_name):
|
||||||
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||||
domain.generate_dkim_key()
|
domain.generate_dkim_key()
|
||||||
|
models.db.session.add(domain)
|
||||||
|
models.db.session.commit()
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for(".domain_details", domain_name=domain_name))
|
flask.url_for(".domain_details", domain_name=domain_name))
|
||||||
|
|
||||||
|
@ -2,34 +2,30 @@
|
|||||||
"name": "mailu",
|
"name": "mailu",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Mailu admin assets",
|
"description": "Mailu admin assets",
|
||||||
"main": "assest/index.js",
|
"main": "assets/index.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.4.4",
|
"@babel/core": "^7.14.6",
|
||||||
"@babel/preset-env": "^7.4.4",
|
"admin-lte": "^2.4.18",
|
||||||
"admin-lte": "^2.4.10",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-loader": "^8.0.5",
|
|
||||||
"bootstrap": "^3.4.1",
|
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^2.1.1",
|
||||||
"expose-loader": "^0.7.5",
|
"expose-loader": "^0.7.5",
|
||||||
"file-loader": "^3.0.1",
|
"jquery": "^3.6.0",
|
||||||
"font-awesome": "^4.7.0",
|
"less": "^3.13.1",
|
||||||
"font-awesome-loader": "^1.0.2",
|
|
||||||
"jQuery": "^1.7.4",
|
|
||||||
"less": "^3.9.0",
|
|
||||||
"less-loader": "^5.0.0",
|
"less-loader": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "^0.6.0",
|
"mini-css-extract-plugin": "^1.2.1",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.13.1",
|
||||||
"popper.js": "^1.15.0",
|
"sass-loader": "^7.3.1",
|
||||||
"sass-loader": "^7.1.0",
|
"select2": "^4.0.13",
|
||||||
"select2": "^4.0.7-rc.0",
|
"url-loader": "^2.3.0",
|
||||||
"style-loader": "^0.23.1",
|
"webpack": "^4.33.0",
|
||||||
"url-loader": "^1.1.2",
|
"webpack-cli": "^3.3.12"
|
||||||
"webpack": "^4.30.0",
|
|
||||||
"webpack-cli": "^3.3.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ bcrypt==3.1.6
|
|||||||
blinker==1.4
|
blinker==1.4
|
||||||
cffi==1.12.3
|
cffi==1.12.3
|
||||||
Click==7.0
|
Click==7.0
|
||||||
cryptography==3.2
|
cryptography==3.4.7
|
||||||
decorator==4.4.0
|
decorator==4.4.0
|
||||||
dnspython==1.16.0
|
dnspython==1.16.0
|
||||||
dominate==2.3.5
|
dominate==2.3.5
|
||||||
@ -25,7 +25,7 @@ idna==2.8
|
|||||||
infinity==1.4
|
infinity==1.4
|
||||||
intervals==0.8.1
|
intervals==0.8.1
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.10.1
|
Jinja2==2.11.3
|
||||||
limits==1.3
|
limits==1.3
|
||||||
Mako==1.0.9
|
Mako==1.0.9
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
@ -36,11 +36,11 @@ passlib==1.7.4
|
|||||||
psycopg2==2.8.2
|
psycopg2==2.8.2
|
||||||
pycparser==2.19
|
pycparser==2.19
|
||||||
Pygments==2.8.1
|
Pygments==2.8.1
|
||||||
pyOpenSSL==19.0.0
|
pyOpenSSL==20.0.1
|
||||||
python-dateutil==2.8.0
|
python-dateutil==2.8.0
|
||||||
python-editor==1.0.4
|
python-editor==1.0.4
|
||||||
pytz==2019.1
|
pytz==2019.1
|
||||||
PyYAML==5.1
|
PyYAML==5.4.1
|
||||||
redis==3.2.1
|
redis==3.2.1
|
||||||
#alpine3:12 provides six==1.15.0
|
#alpine3:12 provides six==1.15.0
|
||||||
#six==1.12.0
|
#six==1.12.0
|
||||||
|
@ -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))
|
os.system("flask mailu admin %s %s '%s' --mode %s" % (account, domain, password, mode))
|
||||||
|
|
||||||
start_command="".join([
|
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 "",
|
"--access-logfile - " if (log.root.level<=log.INFO) else "",
|
||||||
"--error-logfile - ",
|
"--error-logfile - ",
|
||||||
"--preload ",
|
"--preload ",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# Remove the first line of the Received: header. Note that we cannot fully remove the Received: header
|
# 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
|
# 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.
|
# 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.
|
# Remove other typically private information.
|
||||||
/^\s*User-Agent:/ IGNORE
|
/^\s*User-Agent:/ IGNORE
|
||||||
|
@ -8,12 +8,13 @@ import logging as log
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from podop import run_server
|
from podop import run_server
|
||||||
|
from pwd import getpwnam
|
||||||
from socrate import system, conf
|
from socrate import system, conf
|
||||||
|
|
||||||
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
|
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
|
||||||
|
|
||||||
def start_podop():
|
def start_podop():
|
||||||
os.setuid(100)
|
os.setuid(getpwnam('postfix').pw_uid)
|
||||||
url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/"
|
url = "http://" + os.environ["ADMIN_ADDRESS"] + "/internal/postfix/"
|
||||||
# TODO: Remove verbosity setting from Podop?
|
# TODO: Remove verbosity setting from Podop?
|
||||||
run_server(0, "postfix", "/tmp/podop.socket", [
|
run_server(0, "postfix", "/tmp/podop.socket", [
|
||||||
@ -36,6 +37,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["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["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["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"):
|
for postfix_file in glob.glob("/conf/*.cf"):
|
||||||
conf.jinja(postfix_file, os.environ, os.path.join("/etc/postfix", os.path.basename(postfix_file)))
|
conf.jinja(postfix_file, os.environ, os.path.join("/etc/postfix", os.path.basename(postfix_file)))
|
||||||
|
@ -497,6 +497,8 @@ follow these steps:
|
|||||||
|
|
||||||
logging:
|
logging:
|
||||||
driver: journald
|
driver: journald
|
||||||
|
options:
|
||||||
|
tag: mailu-front
|
||||||
|
|
||||||
2. Add the /etc/fail2ban/filter.d/bad-auth.conf
|
2. Add the /etc/fail2ban/filter.d/bad-auth.conf
|
||||||
|
|
||||||
@ -506,6 +508,7 @@ follow these steps:
|
|||||||
[Definition]
|
[Definition]
|
||||||
failregex = .* client login failed: .+ client:\ <HOST>
|
failregex = .* client login failed: .+ client:\ <HOST>
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
journalmatch = CONTAINER_TAG=mailu-front
|
||||||
|
|
||||||
3. Add the /etc/fail2ban/jail.d/bad-auth.conf
|
3. Add the /etc/fail2ban/jail.d/bad-auth.conf
|
||||||
|
|
||||||
@ -513,8 +516,8 @@ follow these steps:
|
|||||||
|
|
||||||
[bad-auth]
|
[bad-auth]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
backend = systemd
|
||||||
filter = bad-auth
|
filter = bad-auth
|
||||||
logpath = /var/log/messages
|
|
||||||
bantime = 604800
|
bantime = 604800
|
||||||
findtime = 300
|
findtime = 300
|
||||||
maxretry = 10
|
maxretry = 10
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
apiVersion: apps/v1beta2
|
apiVersion: apps/v1
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
metadata:
|
metadata:
|
||||||
name: mailu-front
|
name: mailu-front
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
flask
|
Flask==1.0.2
|
||||||
flask-bootstrap
|
Flask-Bootstrap==3.3.7.1
|
||||||
redis
|
gunicorn==19.9.0
|
||||||
gunicorn
|
redis==3.2.1
|
||||||
|
@ -54,11 +54,11 @@ def build_app(path):
|
|||||||
@app.context_processor
|
@app.context_processor
|
||||||
def app_context():
|
def app_context():
|
||||||
return dict(
|
return dict(
|
||||||
versions=os.getenv("VERSIONS","master").split(','),
|
versions=os.getenv("VERSIONS","master").split(','),
|
||||||
stable_version = os.getenv("stable_version", "master")
|
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([
|
prefix_bp.jinja_loader = jinja2.ChoiceLoader([
|
||||||
jinja2.FileSystemLoader(os.path.join(path, "templates")),
|
jinja2.FileSystemLoader(os.path.join(path, "templates")),
|
||||||
jinja2.FileSystemLoader(os.path.join(path, "flavors"))
|
jinja2.FileSystemLoader(os.path.join(path, "flavors"))
|
||||||
|
@ -59,7 +59,7 @@ the security implications caused by such an increase of attack surface.<p>
|
|||||||
<i>Fetchmail allows users to retrieve mail from an external mail-server via IMAP/POP3 and puts it in their inbox.</i>
|
<i>Fetchmail allows users to retrieve mail from an external mail-server via IMAP/POP3 and puts it in their inbox.</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ manage your email domains, users, etc.</p>
|
|||||||
<input class="form-control" type="text" name="admin_path" id="admin_path" style="display: none">
|
<input class="form-control" type="text" name="admin_path" id="admin_path" style="display: none">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ the security implications caused by such an increase of attack surface.<p>
|
|||||||
<i>Fetchmail allows users to retrieve mail from an external mail-server via IMAP/POP3 and puts it in their inbox.</i>
|
<i>Fetchmail allows users to retrieve mail from an external mail-server via IMAP/POP3 and puts it in their inbox.</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
||||||
|
|
||||||
|
@ -3,14 +3,5 @@
|
|||||||
# Skip deploy for staging branch
|
# Skip deploy for staging branch
|
||||||
[ "$TRAVIS_BRANCH" = "staging" ] && exit 0
|
[ "$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 login -u $DOCKER_UN -p $DOCKER_PW
|
||||||
docker-compose -f tests/build.yml push
|
docker-compose -f tests/build.yml push
|
||||||
|
1
towncrier/newsfragments/1294.bugfix
Normal file
1
towncrier/newsfragments/1294.bugfix
Normal file
@ -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)
|
1
towncrier/newsfragments/1845.feature
Normal file
1
towncrier/newsfragments/1845.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Update version of rainloop webmail to 1.16.0. This is a security update.
|
1
towncrier/newsfragments/1857.doc
Normal file
1
towncrier/newsfragments/1857.doc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Update fail2ban documentation to use systemd backend instead of filepath for journald
|
1
towncrier/newsfragments/1861.bugfix
Normal file
1
towncrier/newsfragments/1861.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix a bug preventing colons from being used in passwords when using radicale/webdav.
|
1
towncrier/newsfragments/1874.bugfix
Normal file
1
towncrier/newsfragments/1874.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Remove dot in blueprint name to prevent critical flask startup error in setup.
|
1
towncrier/newsfragments/1880.feature
Normal file
1
towncrier/newsfragments/1880.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Update jquery used in setup. Set pinned versions in requirements.txt for setup. This is a security update.
|
1
towncrier/newsfragments/191.bugfix
Normal file
1
towncrier/newsfragments/191.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Replace PUBLIC_HOSTNAME and PUBLIC_IP in "Received" headers to ensure that no undue spam points are attributed
|
@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
# Shared layer between nginx, dovecot, postfix, postgresql, rspamd, unbound, rainloop, roundcube
|
# Shared layer between nginx, dovecot, postfix, postgresql, rspamd, unbound, rainloop, roundcube
|
||||||
RUN pip3 install socrate
|
RUN pip3 install socrate
|
||||||
|
|
||||||
ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.14.0/rainloop-community-1.14.0.zip
|
ENV RAINLOOP_URL https://github.com/RainLoop/rainloop-webmail/releases/download/v1.16.0/rainloop-community-1.16.0.zip
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
unzip python3-jinja2 \
|
unzip python3-jinja2 \
|
||||||
|
Reference in New Issue
Block a user