diff --git a/core/admin/Dockerfile b/core/admin/Dockerfile index 08de0e88..083bec80 100644 --- a/core/admin/Dockerfile +++ b/core/admin/Dockerfile @@ -4,10 +4,10 @@ RUN mkdir -p /app WORKDIR /app COPY requirements-prod.txt requirements.txt -RUN apk add --no-cache openssl \ +RUN apk add --no-cache openssl curl \ && apk add --no-cache --virtual build-dep openssl-dev libffi-dev python-dev build-base \ && pip install -r requirements.txt \ - && apk del build-dep + && apk del --no-cache build-dep COPY mailu ./mailu COPY migrations ./migrations @@ -20,3 +20,5 @@ EXPOSE 80/tcp VOLUME ["/data"] CMD ["/start.sh"] + +HEALTHCHECK CMD curl -f -L http://localhost/ui || exit 1 diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index a3f0353c..a73e6ab9 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -11,9 +11,8 @@ import os import docker import socket import uuid -import redis -from werkzeug.contrib import fixers +from werkzeug.contrib import fixers, profiler # Create application app = flask.Flask(__name__) @@ -58,12 +57,15 @@ default_config = { 'RECAPTCHA_PUBLIC_KEY': '', 'RECAPTCHA_PRIVATE_KEY': '', # Advanced settings - 'PASSWORD_SCHEME': 'SHA512-CRYPT', + 'PASSWORD_SCHEME': 'BLF-CRYPT', # Host settings 'HOST_IMAP': 'imap', 'HOST_POP3': 'imap', 'HOST_SMTP': 'smtp', + 'HOST_WEBMAIL': 'webmail', + 'HOST_FRONT': 'front', 'HOST_AUTHSMTP': os.environ.get('HOST_SMTP', 'smtp'), + 'POD_ADDRESS_RANGE': None } # Load configuration from the environment if available @@ -81,6 +83,10 @@ if app.config.get("DEBUG"): import flask_debugtoolbar toolbar = flask_debugtoolbar.DebugToolbarExtension(app) +# Profiler +if app.config.get("DEBUG"): + app.wsgi_app = profiler.ProfilerMiddleware(app.wsgi_app, restrictions=[30]) + # Manager commnad manager = flask_script.Manager(app) manager.add_command('db', flask_migrate.MigrateCommand) @@ -89,9 +95,6 @@ manager.add_command('db', flask_migrate.MigrateCommand) babel = flask_babel.Babel(app) translations = list(map(str, babel.list_translations())) -# Quota manager -quota = redis.Redis.from_url(app.config.get("QUOTA_STORAGE_URL")) - @babel.localeselector def get_locale(): return flask.request.accept_languages.best_match(translations) @@ -133,4 +136,5 @@ class PrefixMiddleware(object): environ['SCRIPT_NAME'] = prefix return self.app(environ, start_response) + app.wsgi_app = PrefixMiddleware(fixers.ProxyFix(app.wsgi_app)) diff --git a/core/admin/mailu/internal/__init__.py b/core/admin/mailu/internal/__init__.py index 6419ad10..80a3c754 100644 --- a/core/admin/mailu/internal/__init__.py +++ b/core/admin/mailu/internal/__init__.py @@ -6,7 +6,8 @@ import socket import flask -internal = flask.Blueprint('internal', __name__) +internal = flask.Blueprint('internal', __name__, template_folder='templates') + @internal.app_errorhandler(RateLimitExceeded) def rate_limit_handler(e): @@ -17,6 +18,7 @@ def rate_limit_handler(e): response.headers['Auth-Wait'] = '3' return response + @limiter.request_filter def whitelist_webmail(): try: @@ -26,4 +28,4 @@ def whitelist_webmail(): return False -from mailu.internal import views +from mailu.internal.views import * diff --git a/core/dovecot/sieve/before.sieve b/core/admin/mailu/internal/templates/default.sieve similarity index 55% rename from core/dovecot/sieve/before.sieve rename to core/admin/mailu/internal/templates/default.sieve index 6ebc20c5..d771ee99 100644 --- a/core/dovecot/sieve/before.sieve +++ b/core/admin/mailu/internal/templates/default.sieve @@ -8,8 +8,6 @@ require "regex"; require "relational"; require "date"; require "comparator-i;ascii-numeric"; -require "vnd.dovecot.extdata"; -require "vnd.dovecot.execute"; require "spamtestplus"; require "editheader"; require "index"; @@ -20,21 +18,20 @@ if header :index 2 :matches "Received" "from * by * for <*>; *" addheader "Delivered-To" "<${3}>"; } -if allof (string :is "${extdata.spam_enabled}" "1", - spamtest :percent :value "gt" :comparator "i;ascii-numeric" "${extdata.spam_threshold}") +{% if user.spam_enabled %} +if spamtest :percent :value "gt" :comparator "i;ascii-numeric" "{{ user.spam_threshold }}" { setflag "\\seen"; fileinto :create "Junk"; stop; } +{% endif %} if exists "X-Virus" { discard; stop; } -if allof (string :is "${extdata.reply_enabled}" "1", - currentdate :value "le" "date" "${extdata.reply_enddate}") -{ - vacation :days 1 :subject "${extdata.reply_subject}" "${extdata.reply_body}"; -} +{% if user.reply_active %} +vacation :days 1 :subject "{{ user.reply_subject }}" "{{ user.reply_body }}"; +{% endif %} diff --git a/core/admin/mailu/internal/views/__init__.py b/core/admin/mailu/internal/views/__init__.py new file mode 100644 index 00000000..a32106c0 --- /dev/null +++ b/core/admin/mailu/internal/views/__init__.py @@ -0,0 +1,3 @@ +__all__ = [ + 'auth', 'postfix', 'dovecot', 'fetch' +] diff --git a/core/admin/mailu/internal/views.py b/core/admin/mailu/internal/views/auth.py similarity index 99% rename from core/admin/mailu/internal/views.py rename to core/admin/mailu/internal/views/auth.py index b97d329e..823fbd40 100644 --- a/core/admin/mailu/internal/views.py +++ b/core/admin/mailu/internal/views/auth.py @@ -4,7 +4,6 @@ from mailu.internal import internal, nginx import flask import flask_login import base64 -import urllib @internal.route("/auth/email") diff --git a/core/admin/mailu/internal/views/dovecot.py b/core/admin/mailu/internal/views/dovecot.py new file mode 100644 index 00000000..036140f0 --- /dev/null +++ b/core/admin/mailu/internal/views/dovecot.py @@ -0,0 +1,50 @@ +from mailu import db, models, app +from mailu.internal import internal + +import flask +import socket + + +@internal.route("/dovecot/passdb/") +def dovecot_passdb_dict(user_email): + user = models.User.query.get(user_email) or flask.abort(404) + allow_nets = [] + allow_nets.append( + app.config.get("POD_ADDRESS_RANGE") or + socket.gethostbyname(app.config["HOST_FRONT"]) + ) + allow_nets.append(socket.gethostbyname(app.config["HOST_WEBMAIL"])) + print(allow_nets) + return flask.jsonify({ + "password": None, + "nopassword": "Y", + "allow_nets": ",".join(allow_nets) + }) + + +@internal.route("/dovecot/userdb/") +def dovecot_userdb_dict(user_email): + user = models.User.query.get(user_email) or flask.abort(404) + return flask.jsonify({ + "quota_rule": "*:bytes={}".format(user.quota_bytes) + }) + + +@internal.route("/dovecot/quota//", methods=["POST"]) +def dovecot_quota(ns, user_email): + user = models.User.query.get(user_email) or flask.abort(404) + if ns == "storage": + user.quota_bytes_used = flask.request.get_json() + db.session.commit() + return flask.jsonify(None) + + +@internal.route("/dovecot/sieve/name/