diff --git a/core/admin/assets/Dockerfile b/core/admin/assets/Dockerfile
index 613aa6c0..33b9bdb9 100644
--- a/core/admin/assets/Dockerfile
+++ b/core/admin/assets/Dockerfile
@@ -5,7 +5,6 @@ FROM node:16-alpine3.16
WORKDIR /work
COPY package.json ./
-COPY webpack.config.js ./
RUN set -euxo pipefail \
; npm config set update-notifier false \
@@ -17,6 +16,7 @@ RUN set -euxo pipefail \
done
COPY assets/ ./assets/
+COPY webpack.config.js ./
RUN set -euxo pipefail \
; node_modules/.bin/webpack-cli --color
diff --git a/core/admin/assets/assets/app.js b/core/admin/assets/assets/app.js
index ad2b543a..661e0242 100644
--- a/core/admin/assets/assets/app.js
+++ b/core/admin/assets/assets/app.js
@@ -1,8 +1,3 @@
-require('./app.css');
-
-import logo from './mailu.png';
-import modules from "./*.json";
-
// Inspired from https://github.com/mehdibo/hibp-js/blob/master/hibp.js
function sha1(string) {
var buffer = new TextEncoder("utf-8").encode(string);
diff --git a/core/admin/assets/assets/vendor.js b/core/admin/assets/assets/vendor.js
index 906448cf..819240ad 100644
--- a/core/admin/assets/assets/vendor.js
+++ b/core/admin/assets/assets/vendor.js
@@ -1,5 +1,5 @@
// AdminLTE
-import 'admin-lte/plugins/jquery/jquery.min.js';
+window.$ = window.jQuery = require('admin-lte/plugins/jquery/jquery.min.js');
import 'admin-lte/plugins/bootstrap/js/bootstrap.bundle.min.js';
import 'admin-lte/build/scss/adminlte.scss';
import 'admin-lte/build/js/AdminLTE.js';
@@ -18,7 +18,7 @@ import 'admin-lte/plugins/datatables/jquery.dataTables.min.js';
import 'admin-lte/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js';
import 'admin-lte/plugins/datatables-responsive/js/dataTables.responsive.min.js';
import 'admin-lte/plugins/datatables-responsive/js/responsive.bootstrap4.min.js';
+import modules from "./*.json";
// clipboard.js
-import 'clipboard/dist/clipboard.min.js';
-
+window.ClipboardJS = require('clipboard/dist/clipboard.min.js');
diff --git a/core/admin/assets/webpack.config.js b/core/admin/assets/webpack.config.js
index 25c966c1..8c3d6c27 100644
--- a/core/admin/assets/webpack.config.js
+++ b/core/admin/assets/webpack.config.js
@@ -9,7 +9,7 @@ module.exports = {
mode: 'production',
entry: {
app: {
- import: './assets/app.js',
+ import: ['./assets/app.css', './assets/mailu.png', './assets/app.js'],
dependOn: 'vendor',
},
vendor: './assets/vendor.js',
diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py
index b941e95c..d282bfc2 100644
--- a/core/admin/mailu/configuration.py
+++ b/core/admin/mailu/configuration.py
@@ -13,17 +13,19 @@ DEFAULT_CONFIG = {
'RATELIMIT_STORAGE_URL': '',
'DEBUG': False,
'DEBUG_PROFILER': False,
+ 'DEBUG_TB_INTERCEPT_REDIRECTS': False,
'DEBUG_ASSETS': '',
'DOMAIN_REGISTRATION': False,
'TEMPLATES_AUTO_RELOAD': True,
'MEMORY_SESSIONS': False,
+ 'FETCHMAIL_ENABLED': False,
# Database settings
'DB_FLAVOR': None,
'DB_USER': 'mailu',
'DB_PW': None,
'DB_HOST': 'database',
'DB_NAME': 'mailu',
- 'SQLITE_DATABASE_FILE':'data/main.db',
+ 'SQLITE_DATABASE_FILE': 'data/main.db',
'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
# Statistics management
@@ -60,7 +62,7 @@ DEFAULT_CONFIG = {
# Web settings
'SITENAME': 'Mailu',
'WEBSITE': 'https://mailu.io',
- 'ADMIN' : 'none',
+ 'ADMIN': 'none',
'WEB_ADMIN': '/admin',
'WEB_WEBMAIL': '/webmail',
'WEBMAIL': 'none',
@@ -73,7 +75,7 @@ DEFAULT_CONFIG = {
'SESSION_KEY_BITS': 128,
'SESSION_TIMEOUT': 3600,
'PERMANENT_SESSION_LIFETIME': 30*24*3600,
- 'SESSION_COOKIE_SECURE': True,
+ 'SESSION_COOKIE_SECURE': None,
'CREDENTIAL_ROUNDS': 12,
'TLS_PERMISSIVE': True,
'TZ': 'Etc/UTC',
@@ -156,6 +158,8 @@ class ConfigManager:
self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
self.config['SESSION_COOKIE_HTTPONLY'] = True
+ if self.config['SESSION_COOKIE_SECURE'] is None:
+ self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'
self.config['SESSION_PERMANENT'] = True
self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])
self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
diff --git a/core/admin/mailu/ui/templates/sidebar.html b/core/admin/mailu/ui/templates/sidebar.html
index a57a2abe..54448c8b 100644
--- a/core/admin/mailu/ui/templates/sidebar.html
+++ b/core/admin/mailu/ui/templates/sidebar.html
@@ -31,12 +31,14 @@
{% trans %}Auto-reply{% endtrans %}
+ {%- if config["FETCHMAIL_ENABLED"] %}
{% trans %}Fetched accounts{% endtrans %}
+ {%- endif %}
diff --git a/core/admin/mailu/ui/views/fetches.py b/core/admin/mailu/ui/views/fetches.py
index 69018ba9..3c4d629d 100644
--- a/core/admin/mailu/ui/views/fetches.py
+++ b/core/admin/mailu/ui/views/fetches.py
@@ -1,5 +1,6 @@
from mailu import models, utils
from mailu.ui import ui, forms, access
+from flask import current_app as app
import flask
import flask_login
@@ -10,6 +11,8 @@ import wtforms
@ui.route('/fetch/list/', methods=['GET'])
@access.owner(models.User, 'user_email')
def fetch_list(user_email):
+ if not app.config['FETCHMAIL_ENABLED']:
+ flask.abort(404)
user_email = user_email or flask_login.current_user.email
user = models.User.query.get(user_email) or flask.abort(404)
return flask.render_template('fetch/list.html', user=user)
@@ -19,6 +22,8 @@ def fetch_list(user_email):
@ui.route('/fetch/create/', methods=['GET', 'POST'])
@access.owner(models.User, 'user_email')
def fetch_create(user_email):
+ if not app.config['FETCHMAIL_ENABLED']:
+ flask.abort(404)
user_email = user_email or flask_login.current_user.email
user = models.User.query.get(user_email) or flask.abort(404)
form = forms.FetchForm()
@@ -40,6 +45,8 @@ def fetch_create(user_email):
@ui.route('/fetch/edit/', methods=['GET', 'POST'])
@access.owner(models.Fetch, 'fetch_id')
def fetch_edit(fetch_id):
+ if not app.config['FETCHMAIL_ENABLED']:
+ flask.abort(404)
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
form = forms.FetchForm(obj=fetch)
utils.formatCSVField(form.folders)
@@ -61,6 +68,8 @@ def fetch_edit(fetch_id):
@access.confirmation_required("delete a fetched account")
@access.owner(models.Fetch, 'fetch_id')
def fetch_delete(fetch_id):
+ if not app.config['FETCHMAIL_ENABLED']:
+ flask.abort(404)
fetch = models.Fetch.query.get(fetch_id) or flask.abort(404)
user = fetch.user
models.db.session.delete(fetch)
diff --git a/core/admin/mailu/ui/views/users.py b/core/admin/mailu/ui/views/users.py
index b5e7304c..c7d252a9 100644
--- a/core/admin/mailu/ui/views/users.py
+++ b/core/admin/mailu/ui/views/users.py
@@ -64,10 +64,11 @@ def user_edit(user_email):
form.quota_bytes.validators = [
wtforms.validators.NumberRange(max=max_quota_bytes)]
if form.validate_on_submit():
- if msg := utils.isBadOrPwned(form):
- flask.flash(msg, "error")
- return flask.render_template('user/edit.html', form=form, user=user,
- domain=user.domain, max_quota_bytes=max_quota_bytes)
+ if form.pw.data:
+ if msg := utils.isBadOrPwned(form):
+ flask.flash(msg, "error")
+ return flask.render_template('user/edit.html', form=form, user=user,
+ domain=user.domain, max_quota_bytes=max_quota_bytes)
form.populate_obj(user)
if form.pw.data:
user.set_password(form.pw.data)
diff --git a/core/admin/migrations/versions/f4f0f89e0047_.py b/core/admin/migrations/versions/f4f0f89e0047_.py
index 5843e9d2..8d20063c 100644
--- a/core/admin/migrations/versions/f4f0f89e0047_.py
+++ b/core/admin/migrations/versions/f4f0f89e0047_.py
@@ -1,4 +1,4 @@
-"""empty message
+""" Add fetch.scan and fetch.folders
Revision ID: f4f0f89e0047
Revises: 8f9ea78776f4
diff --git a/core/admin/run_dev.sh b/core/admin/run_dev.sh
index dbe3368a..cf05fba3 100755
--- a/core/admin/run_dev.sh
+++ b/core/admin/run_dev.sh
@@ -73,7 +73,7 @@ ENV \
DEBUG="true" \
DEBUG_PROFILER="${DEV_PROFILER}" \
DEBUG_ASSETS="/app/static" \
- DEBUG_TB_ENABLED="true" \
+ DEBUG_TB_INTERCEPT_REDIRECTS=False \
\
IMAP_ADDRESS="127.0.0.1" \
POP3_ADDRESS="127.0.0.1" \
diff --git a/core/base/Dockerfile b/core/base/Dockerfile
index d5be6a90..25c8bd81 100644
--- a/core/base/Dockerfile
+++ b/core/base/Dockerfile
@@ -12,7 +12,16 @@ ARG MAILU_GID=1000
RUN set -euxo pipefail \
; addgroup -Sg ${MAILU_GID} mailu \
; adduser -Sg ${MAILU_UID} -G mailu -h /app -g "mailu app" -s /bin/bash mailu \
- ; apk add --no-cache bash ca-certificates curl python3 tzdata
+ ; apk add --no-cache bash ca-certificates curl python3 tzdata \
+ ; machine="$(uname -m)" \
+ ; ! [[ "${machine}" == x86_64 ]] \
+ || apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc
+
+ENV LD_PRELOAD=/usr/lib/libhardened_malloc.so
+ENV CXXFLAGS="-g -O2 -fdebug-prefix-map=/app=. -fstack-protector-strong -Wformat -Werror=format-security -fstack-clash-protection -fexceptions"
+ENV CFLAGS="-g -O2 -fdebug-prefix-map=/app=. -fstack-protector-strong -Wformat -Werror=format-security -fstack-clash-protection -fexceptions"
+ENV CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2"
+ENV LDFLAGS="-Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now"
WORKDIR /app
diff --git a/docs/configuration.rst b/docs/configuration.rst
index d411d2c7..1a40bf65 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -104,6 +104,9 @@ support or e.g. mismatching TLS versions to deliver emails to Mailu.
.. _fetchmail:
+When ``FETCHMAIL_ENABLED`` is set to ``True``, the fetchmail functionality is enabled in the admin interface.
+The container itself still needs to be deployed manually. ``FETCHMAIL_ENABLED`` defaults to ``True``.
+
The ``FETCHMAIL_DELAY`` is a delay (in seconds) for the fetchmail service to
go and fetch new email if available. Do not use too short delays if you do not
want to be blacklisted by external services, but not too long delays if you
@@ -287,6 +290,10 @@ The admin service stores configurations in a database.
- ``DB_USER``: the database user for mailu admin service. (when not ``sqlite``)
- ``DB_NAME``: the database name for mailu admin service. (when not ``sqlite``)
+Alternatively, if you need more control, you can use a `DB URL`_ : do not set any of the ``DB_`` settings and set ``SQLALCHEMY_DATABASE_URI`` instead.
+
+.. _`DB URL`: https://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls
+
The roundcube service stores configurations in a database.
- ``ROUNDCUBE_DB_FLAVOR``: the database type for roundcube service. (``sqlite``, ``postgresql``, ``mysql``)
diff --git a/optional/fetchmail/fetchmail.py b/optional/fetchmail/fetchmail.py
index 137a061a..62bd7124 100755
--- a/optional/fetchmail/fetchmail.py
+++ b/optional/fetchmail/fetchmail.py
@@ -119,6 +119,13 @@ if __name__ == "__main__":
os.setgid(id_fetchmail.pw_gid)
os.setuid(id_fetchmail.pw_uid)
while True:
- time.sleep(int(os.environ.get("FETCHMAIL_DELAY", 60)))
+ delay = int(os.environ.get("FETCHMAIL_DELAY", 60))
+ print("Sleeping for {} seconds".format(delay))
+ time.sleep(delay)
+
+ if not os.environ.get("FETCHMAIL_ENABLED", 'True') in ('True', 'true'):
+ print("Fetchmail disabled, skipping...")
+ continue
+
run(os.environ.get("DEBUG", None) == "True")
sys.stdout.flush()
diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env
index df8c4a4d..cc99912e 100644
--- a/setup/flavors/compose/mailu.env
+++ b/setup/flavors/compose/mailu.env
@@ -79,6 +79,9 @@ RELAYNETS=
# Will relay all outgoing mails if configured
RELAYHOST={{ relayhost }}
+# Show fetchmail functionality in admin interface
+FETCHMAIL_ENABLED={{ fetchmail_enabled or 'False' }}
+
# Fetchmail delay
FETCHMAIL_DELAY={{ fetchmail_delay or '600' }}
diff --git a/towncrier/newsfragments/2127.feature b/towncrier/newsfragments/2127.feature
new file mode 100644
index 00000000..dd4951dd
--- /dev/null
+++ b/towncrier/newsfragments/2127.feature
@@ -0,0 +1 @@
+Add FETCHMAIL_ENABLED to toggle the fetchmail functionality in the admin interface
\ No newline at end of file
diff --git a/towncrier/newsfragments/2525.feature b/towncrier/newsfragments/2525.feature
new file mode 100644
index 00000000..634733c7
--- /dev/null
+++ b/towncrier/newsfragments/2525.feature
@@ -0,0 +1 @@
+Switch to GrapheneOS's hardened_malloc