diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index 3fdd4650..a3f0353c 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -11,6 +11,7 @@ import os import docker import socket import uuid +import redis from werkzeug.contrib import fixers @@ -26,6 +27,7 @@ default_config = { 'BABEL_DEFAULT_TIMEZONE': 'UTC', 'BOOTSTRAP_SERVE_LOCAL': True, 'RATELIMIT_STORAGE_URL': 'redis://redis/2', + 'QUOTA_STORAGE_URL': 'redis://redis/1', 'DEBUG': False, 'DOMAIN_REGISTRATION': False, # Statistics management @@ -87,6 +89,9 @@ 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) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 62796b72..2bd0f04f 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -1,4 +1,4 @@ -from mailu import app, db, dkim, login_manager +from mailu import app, db, dkim, login_manager, quota from sqlalchemy.ext import declarative from passlib import context, hash @@ -20,9 +20,8 @@ class IdnaDomain(db.TypeDecorator): impl = db.String(80) - def process_bind_param(self, value, dialect): - return idna.encode(value) + return idna.encode(value).decode("ascii") def process_result_value(self, value, dialect): return idna.decode(value) @@ -34,31 +33,19 @@ class IdnaEmail(db.TypeDecorator): impl = db.String(255, collation="NOCASE") - def process_bind_param(self, value, dialect): localpart, domain_name = value.split('@') - - email = "{0}@{1}".format( + return "{0}@{1}".format( localpart, idna.encode(domain_name).decode('ascii'), ) - return email def process_result_value(self, value, dialect): localpart, domain_name = value.split('@') - - email = "{0}@{1}".format( + return "{0}@{1}".format( localpart, idna.decode(domain_name), ) - return email - - -# Many-to-many association table for domain managers -managers = db.Table('manager', - db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')), - db.Column('user_email', IdnaEmail, db.ForeignKey('user.email')) -) class CommaSeparatedList(db.TypeDecorator): @@ -67,7 +54,6 @@ class CommaSeparatedList(db.TypeDecorator): impl = db.String - def process_bind_param(self, value, dialect): if type(value) is not list: raise TypeError("Shoud be a list") @@ -80,6 +66,13 @@ class CommaSeparatedList(db.TypeDecorator): return filter(bool, value.split(",")) +# Many-to-many association table for domain managers +managers = db.Table('manager', + db.Column('domain_name', IdnaDomain, db.ForeignKey('domain.name')), + db.Column('user_email', IdnaEmail, db.ForeignKey('user.email')) +) + + class Base(db.Model): """ Base class for all models """ @@ -264,6 +257,10 @@ class User(Base, Email): def get_id(self): return self.email + @property + def quota_bytes_used(self): + return quota.get(self.email) or 0 + scheme_dict = {'SHA512-CRYPT': "sha512_crypt", 'SHA256-CRYPT': "sha256_crypt", 'MD5-CRYPT': "md5_crypt", diff --git a/core/admin/mailu/ui/templates/user/list.html b/core/admin/mailu/ui/templates/user/list.html index 8d3a0304..8a8fad69 100644 --- a/core/admin/mailu/ui/templates/user/list.html +++ b/core/admin/mailu/ui/templates/user/list.html @@ -40,7 +40,7 @@ {% if user.enable_imap %}imap{% endif %} {% if user.enable_pop %}pop3{% endif %} - {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} + {{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} {{ user.comment or '-' }} {{ user.created_at }} {{ user.updated_at or '' }} diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 2ea88255..31271750 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -5,7 +5,7 @@ log_path = /dev/stderr protocols = imap pop3 lmtp sieve postmaster_address = {{ POSTMASTER }}@{{ DOMAIN }} hostname = {{ HOSTNAMES.split(",")[0] }} -mail_plugins = $mail_plugins quota +mail_plugins = $mail_plugins quota quota_clone submission_host = front service dict { @@ -119,6 +119,7 @@ service lmtp { plugin { quota = maildir:User quota + quota_clone_dict = redis:host={{ REDIS_ADDRESS }}:db=1 } diff --git a/core/dovecot/start.py b/core/dovecot/start.py index 8646da89..2feaf912 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -9,6 +9,7 @@ convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read() # Actual startup script os.environ["FRONT_ADDRESS"] = socket.gethostbyname("front") +os.environ["REDIS_ADDRESS"] = socket.gethostbyname("redis") if os.environ["WEBMAIL"] != "none": os.environ["WEBMAIL_ADDRESS"] = socket.gethostbyname("webmail")