diff --git a/.mergify.yml b/.mergify.yml index c1141a93..2af387ed 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -27,7 +27,7 @@ pull_request_rules: - name: Trusted author and 1 approved review; trigger bors r+ conditions: - - author~=^(kaiyou|muhlemmer|mildred|HorayNarea|adi90x|hoellen|ofthesun9|Nebukadneza|micw|lub|Diman0)$ + - author~=^(mergify|kaiyou|muhlemmer|mildred|HorayNarea|adi90x|hoellen|ofthesun9|Nebukadneza|micw|lub|Diman0)$ - -title~=(WIP|wip) - -label~=^(status/wip|status/blocked|review/need2)$ - "#approved-reviews-by>=1" diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index 2cf6a478..429e778c 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -52,8 +52,9 @@ DEFAULT_CONFIG = { 'RECAPTCHA_PUBLIC_KEY': '', 'RECAPTCHA_PRIVATE_KEY': '', # Advanced settings - 'PASSWORD_SCHEME': 'PBKDF2', 'LOG_LEVEL': 'WARNING', + 'SESSION_COOKIE_SECURE': True, + 'CREDENTIAL_ROUNDS': 12, # Host settings 'HOST_IMAP': 'imap', 'HOST_LMTP': 'imap:2525', @@ -100,6 +101,15 @@ class ConfigManager(dict): if self.config["WEBMAIL"] != "none": self.config["WEBMAIL_ADDRESS"] = self.get_host_address("WEBMAIL") + def __get_env(self, key, value): + key_file = key + "_FILE" + if key_file in os.environ: + with open(os.environ.get(key_file)) as file: + value_from_file = file.read() + return value_from_file.strip() + else: + return os.environ.get(key, value) + def __coerce_value(self, value): if isinstance(value, str) and value.lower() in ('true','yes'): return True @@ -111,7 +121,7 @@ class ConfigManager(dict): self.config.update(app.config) # get environment variables self.config.update({ - key: self.__coerce_value(os.environ.get(key, value)) + key: self.__coerce_value(self.__get_env(key, value)) for key, value in DEFAULT_CONFIG.items() }) self.resolve_hosts() @@ -123,6 +133,8 @@ class ConfigManager(dict): self.config['RATELIMIT_STORAGE_URL'] = 'redis://{0}/2'.format(self.config['REDIS_ADDRESS']) self.config['QUOTA_STORAGE_URL'] = 'redis://{0}/1'.format(self.config['REDIS_ADDRESS']) + self.config['SESSION_COOKIE_SAMESITE'] = 'Strict' + self.config['SESSION_COOKIE_HTTPONLY'] = True # update the app config itself app.config = self diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 1e0b16c2..f9ebbf13 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -19,6 +19,20 @@ STATUSES = { }), } +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 + # All tokens are 32 characters hex lowercase + if len(password) == 32: + for token in user.tokens: + if (token.check_password(password) and + (not token.ip or token.ip == ip)): + is_ok = True + break + if not is_ok and user.check_password(password): + is_ok = True + return is_ok def handle_authentication(headers): """ Handle an HTTP nginx authentication request @@ -47,20 +61,7 @@ def handle_authentication(headers): password = raw_password.encode("iso8859-1").decode("utf8") ip = urllib.parse.unquote(headers["Client-Ip"]) user = models.User.query.get(user_email) - status = False - if user: - for token in user.tokens: - if (token.check_password(password) and - (not token.ip or token.ip == ip)): - status = True - if user.check_password(password): - status = True - if status: - if protocol == "imap" and not user.enable_imap: - status = False - elif protocol == "pop3" and not user.enable_pop: - status = False - if status and user.enabled: + if check_credentials(user, password, ip, protocol): return { "Auth-Status": "OK", "Auth-Server": server, diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py index 825dba56..edd62e37 100644 --- a/core/admin/mailu/internal/views/auth.py +++ b/core/admin/mailu/internal/views/auth.py @@ -53,7 +53,7 @@ def basic_authentication(): encoded = authorization.replace("Basic ", "") user_email, password = base64.b64decode(encoded).split(b":") user = models.User.query.get(user_email.decode("utf8")) - if user and user.enabled and user.check_password(password.decode("utf8")): + if nginx.check_credentials(user, password.decode('utf-8'), flask.request.remote_addr, "web"): response = flask.Response() response.headers["X-User"] = user.email return response diff --git a/core/admin/mailu/manage.py b/core/admin/mailu/manage.py index f9add0f4..f9c58d26 100644 --- a/core/admin/mailu/manage.py +++ b/core/admin/mailu/manage.py @@ -92,13 +92,10 @@ def admin(localpart, domain_name, password, mode='create'): @click.argument('localpart') @click.argument('domain_name') @click.argument('password') -@click.argument('hash_scheme', required=False) @with_appcontext -def user(localpart, domain_name, password, hash_scheme=None): +def user(localpart, domain_name, password): """ Create a user """ - if hash_scheme is None: - hash_scheme = app.config['PASSWORD_SCHEME'] domain = models.Domain.query.get(domain_name) if not domain: domain = models.Domain(name=domain_name) @@ -108,7 +105,7 @@ def user(localpart, domain_name, password, hash_scheme=None): domain=domain, global_admin=False ) - user.set_password(password, hash_scheme=hash_scheme) + user.set_password(password) db.session.add(user) db.session.commit() @@ -117,17 +114,14 @@ def user(localpart, domain_name, password, hash_scheme=None): @click.argument('localpart') @click.argument('domain_name') @click.argument('password') -@click.argument('hash_scheme', required=False) @with_appcontext -def password(localpart, domain_name, password, hash_scheme=None): +def password(localpart, domain_name, password): """ Change the password of an user """ email = f'{localpart}@{domain_name}' - user = models.User.query.get(email) - if hash_scheme is None: - hash_scheme = app.config['PASSWORD_SCHEME'] + user = models.User.query.get(email) if user: - user.set_password(password, hash_scheme=hash_scheme) + user.set_password(password) else: print(f'User {email} not found.') db.session.commit() @@ -154,13 +148,10 @@ def domain(domain_name, max_users=-1, max_aliases=-1, max_quota_bytes=0): @click.argument('localpart') @click.argument('domain_name') @click.argument('password_hash') -@click.argument('hash_scheme') @with_appcontext -def user_import(localpart, domain_name, password_hash, hash_scheme = None): +def user_import(localpart, domain_name, password_hash): """ Import a user along with password hash """ - if hash_scheme is None: - hash_scheme = app.config['PASSWORD_SCHEME'] domain = models.Domain.query.get(domain_name) if not domain: domain = models.Domain(name=domain_name) @@ -170,7 +161,7 @@ def user_import(localpart, domain_name, password_hash, hash_scheme = None): domain=domain, global_admin=False ) - user.set_password(password_hash, hash_scheme=hash_scheme, raw=True) + user.set_password(password_hash, raw=True) db.session.add(user) db.session.commit() @@ -223,7 +214,6 @@ def config_update(verbose=False, delete_objects=False): localpart = user_config['localpart'] domain_name = user_config['domain'] password_hash = user_config.get('password_hash', None) - hash_scheme = user_config.get('hash_scheme', None) domain = models.Domain.query.get(domain_name) email = f'{localpart}@{domain_name}' optional_params = {} @@ -245,7 +235,7 @@ def config_update(verbose=False, delete_objects=False): else: for k in optional_params: setattr(user, k, optional_params[k]) - user.set_password(password_hash, hash_scheme=hash_scheme, raw=True) + user.set_password(password_hash, raw=True) db.session.add(user) aliases = new_config.get('aliases', []) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index d134086f..b08c5bd2 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -14,6 +14,7 @@ import flask_sqlalchemy import sqlalchemy import passlib.context import passlib.hash +import passlib.registry import idna import dns @@ -422,6 +423,7 @@ class User(Base, Email): """ __tablename__ = 'user' + _ctx = None domain = db.relationship(Domain, backref=db.backref('users', cascade='all, delete-orphan')) @@ -482,47 +484,51 @@ class User(Base, Email): self.reply_enddate > now ) - scheme_dict = { - 'PBKDF2': 'pbkdf2_sha512', - 'BLF-CRYPT': 'bcrypt', - 'SHA512-CRYPT': 'sha512_crypt', - 'SHA256-CRYPT': 'sha256_crypt', - 'MD5-CRYPT': 'md5_crypt', - 'CRYPT': 'des_crypt', - } - @classmethod def get_password_context(cls): - """ Create password context for hashing and verification + """ create password context for hashing and verification """ - return passlib.context.CryptContext( - schemes=cls.scheme_dict.values(), - default=cls.scheme_dict[app.config['PASSWORD_SCHEME']], - ) + if cls._ctx: + return cls._ctx - def check_password(self, plain): - """ Check password against stored hash - Update hash when default scheme has changed + schemes = passlib.registry.list_crypt_handlers() + # scrypt throws a warning if the native wheels aren't found + schemes.remove('scrypt') + # we can't leave plaintext schemes as they will be misidentified + for scheme in schemes: + if scheme.endswith('plaintext'): + schemes.remove(scheme) + cls._ctx = passlib.context.CryptContext( + schemes=schemes, + default='bcrypt_sha256', + bcrypt_sha256__rounds=app.config['CREDENTIAL_ROUNDS'], + deprecated='auto' + ) + return cls._ctx + + def check_password(self, password): + """ verifies password against stored hash + and updates hash if outdated """ - context = self.get_password_context() - hashed = re.match('^({[^}]+})?(.*)$', self.password).group(2) - result = context.verify(plain, hashed) - if result and context.identify(hashed) != context.default_scheme(): - self.set_password(plain) + reference = self.password + # strip {scheme} if that's something mailu has added + # passlib will identify *crypt based hashes just fine + # on its own + if reference.startswith(('{PBKDF2}', '{BLF-CRYPT}', '{SHA512-CRYPT}', '{SHA256-CRYPT}', '{MD5-CRYPT}', '{CRYPT}')): + reference = reference.split('}', 1)[1] + + result, new_hash = User.get_password_context().verify_and_update(password, reference) + if new_hash: + self.password = new_hash db.session.add(self) db.session.commit() return result - def set_password(self, new, hash_scheme=None, raw=False): - """ Set password for user with specified encryption scheme - @new: plain text password to encrypt (or, if raw is True: the hash itself) + def set_password(self, password, raw=False): + """ Set password for user + @password: plain text password to encrypt (or, if raw is True: the hash itself) """ - # for the list of hash schemes see https://wiki2.dovecot.org/Authentication/PasswordSchemes - if hash_scheme is None: - hash_scheme = app.config['PASSWORD_SCHEME'] - if not raw: - new = self.get_password_context().encrypt(new, self.scheme_dict[hash_scheme]) - self.password = f'{{{hash_scheme}}}{new}' + self.password = password if raw else User.get_password_context().hash(password) def get_managed_domains(self): """ return list of domains this user can manage """ @@ -630,12 +636,22 @@ class Token(Base): ip = db.Column(db.String(255)) def check_password(self, password): - """ verifies password against stored hash """ - return passlib.hash.sha256_crypt.verify(password, self.password) + """ verifies password against stored hash + and updates hash if outdated + """ + if self.password.startswith("$5$"): + if passlib.hash.sha256_crypt.verify(password, self.password): + self.set_password(password) + db.session.add(self) + db.session.commit() + return True + return False + return passlib.hash.pbkdf2_sha256.verify(password, self.password) def set_password(self, password): - """ sets password using sha256_crypt(rounds=1000) """ - self.password = passlib.hash.sha256_crypt.using(rounds=1000).hash(password) + """ sets password using pbkdf2_sha256 (1 round) """ + # tokens have 128bits of entropy, they are not bruteforceable + self.password = passlib.hash.pbkdf2_sha256.using(rounds=1).hash(password) def __repr__(self): return f'' diff --git a/core/admin/mailu/translations/ca/LC_MESSAGES/messages.po b/core/admin/mailu/translations/ca/LC_MESSAGES/messages.po index f63b7083..880709f1 100644 --- a/core/admin/mailu/translations/ca/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/ca/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2018-04-22 12:10+0200\n" -"PO-Revision-Date: 2020-04-26 13:09+0000\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" "Last-Translator: Jaume Barber \n" "Language-Team: Catalan \n" @@ -139,7 +139,7 @@ msgstr "Nom per mostrar" #: mailu/ui/forms.py:98 msgid "Enable spam filter" -msgstr "Activeu filtre d'spam" +msgstr "Activeu filtre spam" #: mailu/ui/forms.py:99 msgid "Spam filter tolerance" @@ -204,7 +204,8 @@ msgstr "Àlies" #: mailu/ui/forms.py:138 msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" -msgstr "Feu servir sintaxi tipus SQL (ex. per a agafar tots els àlies)" +msgstr "" +"Feu servir sintaxi tipus SQL (ex. per seleccionar tots els àlies catch-all)" #: mailu/ui/forms.py:145 msgid "Admin email" @@ -246,11 +247,11 @@ msgstr "Mantén els correus al servidor" #: mailu/ui/forms.py:168 msgid "Announcement subject" -msgstr "Tema de l'avís" +msgstr "Tema de la notificació" #: mailu/ui/forms.py:170 msgid "Announcement body" -msgstr "Missatge de l'avís" +msgstr "Missatge de la notificació" #: mailu/ui/forms.py:172 msgid "Send" @@ -258,7 +259,7 @@ msgstr "Envia" #: mailu/ui/templates/announcement.html:4 msgid "Public announcement" -msgstr "Avís públic" +msgstr "Notificació pública" #: mailu/ui/templates/client.html:4 mailu/ui/templates/sidebar.html:82 msgid "Client setup" @@ -304,7 +305,7 @@ msgstr "Resposta automàtica" #: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:26 #: mailu/ui/templates/user/list.html:36 msgid "Fetched accounts" -msgstr "Comptes trobats" +msgstr "Comptes vinculats" #: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/token/list.html:4 msgid "Authentication tokens" @@ -316,7 +317,7 @@ msgstr "Administració" #: mailu/ui/templates/sidebar.html:44 msgid "Announcement" -msgstr "Avís" +msgstr "Notificació" #: mailu/ui/templates/sidebar.html:49 msgid "Administrators" @@ -324,7 +325,7 @@ msgstr "Administradors" #: mailu/ui/templates/sidebar.html:54 msgid "Relayed domains" -msgstr "Dominis tramesos" +msgstr "Dominis traspassats" #: mailu/ui/templates/sidebar.html:59 mailu/ui/templates/user/settings.html:15 msgid "Antispam" @@ -546,18 +547,19 @@ msgid "" " expires." msgstr "" "Si no sabeu configurar un registre MX a la zona DNS,\n" -"contacteu el vostre proveïdor o administrador de DNS. Per favor, espereu \n" +"contacteu amb el vostre proveïdor o administrador de DNS. Per favor, espereu " +"\n" "uns quants minuts despres d'ajustar el registre MX perquè la " "caixet \n" "del servidor local expire." #: mailu/ui/templates/fetch/create.html:4 msgid "Add a fetched account" -msgstr "Afegiu un compte (fetched)" +msgstr "Afegiu un compte extern" #: mailu/ui/templates/fetch/edit.html:4 msgid "Update a fetched account" -msgstr "Actualitzeu un compte (fetched)" +msgstr "Actualitzeu compte extern" #: mailu/ui/templates/fetch/list.html:12 msgid "Add an account" @@ -605,11 +607,11 @@ msgstr "Editeu domini llegat (relayed)" #: mailu/ui/templates/relay/list.html:4 msgid "Relayed domain list" -msgstr "Llista de dominis llegats (relayed)" +msgstr "Llista de dominis traspassats" #: mailu/ui/templates/relay/list.html:9 msgid "New relayed domain" -msgstr "Nou domini llegat (relayed)" +msgstr "Nou domini traspassat" #: mailu/ui/templates/token/create.html:4 msgid "Create an authentication token" @@ -653,7 +655,7 @@ msgstr "Ajustos d'usuari" #: mailu/ui/templates/user/list.html:21 msgid "Features" -msgstr "Funcions" +msgstr "Característiques" #: mailu/ui/templates/user/password.html:4 msgid "Password update" @@ -669,11 +671,11 @@ msgstr "Auto-reenviament" #: mailu/ui/templates/user/signup_domain.html:8 msgid "pick a domain for the new account" -msgstr "tria un domini per al compte nou" +msgstr "trieu un domini per al compte nou" #: mailu/ui/templates/user/signup_domain.html:14 msgid "Domain" -msgstr "Domini" +msgstr "Nom de domini" #: mailu/ui/templates/user/signup_domain.html:15 msgid "Available slots" diff --git a/core/admin/mailu/translations/de/LC_MESSAGES/messages.po b/core/admin/mailu/translations/de/LC_MESSAGES/messages.po index 941c22ef..4ae71561 100644 --- a/core/admin/mailu/translations/de/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/de/LC_MESSAGES/messages.po @@ -1,11 +1,16 @@ msgid "" msgstr "" +"Project-Id-Version: Mailu\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" +"Last-Translator: Anonymous \n" +"Language-Team: German \n" +"Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: POEditor.com\n" -"Project-Id-Version: Mailu\n" -"Language: de\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.0.1\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." @@ -64,7 +69,7 @@ msgstr "Passwort bestätigen" #: mailu/ui/forms.py:80 mailu/ui/templates/user/list.html:22 #: mailu/ui/templates/user/signup_domain.html:16 msgid "Quota" -msgstr "Quota" +msgstr "Kontingent" #: mailu/ui/forms.py:81 msgid "Allow IMAP access" @@ -699,4 +704,3 @@ msgstr "Domain" #: mailu/ui/templates/user/signup_domain.html:15 msgid "Available slots" msgstr "Verfügbare Plätze" - diff --git a/core/admin/mailu/translations/en/LC_MESSAGES/messages.po b/core/admin/mailu/translations/en/LC_MESSAGES/messages.po index 2ada20b1..4db1dbf1 100644 --- a/core/admin/mailu/translations/en/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/en/LC_MESSAGES/messages.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2018-04-22 12:10+0200\n" -"PO-Revision-Date: 2020-03-11 23:03+0000\n" -"Last-Translator: Jae Beojkkoch \n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" +"Last-Translator: Jaume Barber \n" "Language-Team: English \n" "Language: en\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.11.2\n" +"X-Generator: Weblate 4.0.1\n" "Generated-By: Babel 2.5.3\n" #: mailu/ui/forms.py:32 @@ -30,13 +30,13 @@ msgstr "Confirm" #: mailu/ui/forms.py:40 mailu/ui/forms.py:77 msgid "E-mail" -msgstr "" +msgstr "E-mail" #: mailu/ui/forms.py:41 mailu/ui/forms.py:78 mailu/ui/forms.py:90 #: mailu/ui/forms.py:109 mailu/ui/forms.py:162 #: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:59 msgid "Password" -msgstr "" +msgstr "Password" #: mailu/ui/forms.py:42 mailu/ui/templates/login.html:4 #: mailu/ui/templates/sidebar.html:111 @@ -51,7 +51,7 @@ msgstr "" #: mailu/ui/forms.py:47 msgid "Maximum user count" -msgstr "" +msgstr "Maximum user count" #: mailu/ui/forms.py:48 msgid "Maximum alias count" @@ -59,11 +59,11 @@ msgstr "" #: mailu/ui/forms.py:49 msgid "Maximum user quota" -msgstr "" +msgstr "Maximum user quota" #: mailu/ui/forms.py:50 msgid "Enable sign-up" -msgstr "" +msgstr "Enable sign-up" #: mailu/ui/forms.py:51 mailu/ui/forms.py:72 mailu/ui/forms.py:83 #: mailu/ui/forms.py:128 mailu/ui/forms.py:140 @@ -71,28 +71,28 @@ msgstr "" #: mailu/ui/templates/relay/list.html:19 mailu/ui/templates/token/list.html:19 #: mailu/ui/templates/user/list.html:23 msgid "Comment" -msgstr "" +msgstr "Comment" #: mailu/ui/forms.py:52 mailu/ui/forms.py:61 mailu/ui/forms.py:66 #: mailu/ui/forms.py:73 mailu/ui/forms.py:132 mailu/ui/forms.py:141 msgid "Create" -msgstr "" +msgstr "Create" #: mailu/ui/forms.py:57 msgid "Initial admin" -msgstr "" +msgstr "Initial admin" #: mailu/ui/forms.py:58 msgid "Admin password" -msgstr "" +msgstr "Admin password" #: mailu/ui/forms.py:59 mailu/ui/forms.py:79 mailu/ui/forms.py:91 msgid "Confirm password" -msgstr "" +msgstr "Confirm password" #: mailu/ui/forms.py:65 msgid "Alternative name" -msgstr "" +msgstr "Alternative name" #: mailu/ui/forms.py:70 msgid "Relayed domain name" @@ -105,23 +105,23 @@ msgstr "" #: mailu/ui/forms.py:80 mailu/ui/templates/user/list.html:22 #: mailu/ui/templates/user/signup_domain.html:16 msgid "Quota" -msgstr "" +msgstr "Quota" #: mailu/ui/forms.py:81 msgid "Allow IMAP access" -msgstr "" +msgstr "Allow IMAP access" #: mailu/ui/forms.py:82 msgid "Allow POP3 access" -msgstr "" +msgstr "Allow POP3 access" #: mailu/ui/forms.py:84 msgid "Enabled" -msgstr "" +msgstr "Enabled" #: mailu/ui/forms.py:85 msgid "Save" -msgstr "" +msgstr "Save" #: mailu/ui/forms.py:89 msgid "Email address" @@ -131,7 +131,7 @@ msgstr "" #: mailu/ui/templates/user/signup.html:4 #: mailu/ui/templates/user/signup_domain.html:4 msgid "Sign up" -msgstr "" +msgstr "Sign up" #: mailu/ui/forms.py:97 msgid "Displayed name" @@ -139,15 +139,15 @@ msgstr "" #: mailu/ui/forms.py:98 msgid "Enable spam filter" -msgstr "" +msgstr "Enable spam filter" #: mailu/ui/forms.py:99 msgid "Spam filter tolerance" -msgstr "" +msgstr "Spam filter tolerance" #: mailu/ui/forms.py:100 msgid "Enable forwarding" -msgstr "" +msgstr "Enable forwarding" #: mailu/ui/forms.py:101 msgid "Keep a copy of the emails" @@ -160,7 +160,7 @@ msgstr "" #: mailu/ui/forms.py:105 msgid "Save settings" -msgstr "" +msgstr "Save settings" #: mailu/ui/forms.py:110 msgid "Password check" @@ -184,11 +184,11 @@ msgstr "" #: mailu/ui/forms.py:119 msgid "End of vacation" -msgstr "" +msgstr "End of vacation" #: mailu/ui/forms.py:120 msgid "Update" -msgstr "" +msgstr "Update" #: mailu/ui/forms.py:125 msgid "Your token (write it down, as it will never be displayed again)" @@ -196,11 +196,11 @@ msgstr "" #: mailu/ui/forms.py:130 mailu/ui/templates/token/list.html:20 msgid "Authorized IP" -msgstr "" +msgstr "Authorized IP" #: mailu/ui/forms.py:136 msgid "Alias" -msgstr "" +msgstr "Alias" #: mailu/ui/forms.py:138 msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" @@ -229,7 +229,7 @@ msgstr "" #: mailu/ui/forms.py:159 mailu/ui/templates/client.html:20 #: mailu/ui/templates/client.html:47 msgid "TCP port" -msgstr "" +msgstr "TCP port" #: mailu/ui/forms.py:160 msgid "Enable TLS" @@ -283,7 +283,7 @@ msgstr "" #: mailu/ui/templates/docker-error.html:4 msgid "Docker error" -msgstr "" +msgstr "Docker error" #: mailu/ui/templates/docker-error.html:12 msgid "An error occurred while talking to the Docker server." @@ -328,11 +328,11 @@ msgstr "" #: mailu/ui/templates/sidebar.html:54 msgid "Relayed domains" -msgstr "" +msgstr "Relayed domains" #: mailu/ui/templates/sidebar.html:59 mailu/ui/templates/user/settings.html:15 msgid "Antispam" -msgstr "" +msgstr "Antispam" #: mailu/ui/templates/sidebar.html:66 msgid "Mail domains" @@ -593,7 +593,7 @@ msgstr "" #: mailu/ui/templates/relay/create.html:4 msgid "New relay domain" -msgstr "" +msgstr "New relay domain" #: mailu/ui/templates/relay/edit.html:4 msgid "Edit relayd domain" @@ -601,11 +601,11 @@ msgstr "" #: mailu/ui/templates/relay/list.html:4 msgid "Relayed domain list" -msgstr "" +msgstr "Relayed domain list" #: mailu/ui/templates/relay/list.html:9 msgid "New relayed domain" -msgstr "" +msgstr "New relayed domain" #: mailu/ui/templates/token/create.html:4 msgid "Create an authentication token" @@ -669,7 +669,7 @@ msgstr "" #: mailu/ui/templates/user/signup_domain.html:14 msgid "Domain" -msgstr "" +msgstr "Domain" #: mailu/ui/templates/user/signup_domain.html:15 msgid "Available slots" diff --git a/core/admin/mailu/translations/es/LC_MESSAGES/messages.po b/core/admin/mailu/translations/es/LC_MESSAGES/messages.po index 94b39439..c70ed6f5 100644 --- a/core/admin/mailu/translations/es/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/es/LC_MESSAGES/messages.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Mailu\n" -"PO-Revision-Date: 2020-03-11 23:03+0000\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" "Last-Translator: Jaume Barber \n" "Language-Team: Spanish \n" @@ -10,7 +10,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.11.2\n" +"X-Generator: Weblate 4.0.1\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." @@ -425,7 +425,7 @@ msgstr "Añadir una cuenta" #: mailu/ui/templates/fetch/list.html:19 msgid "Endpoint" -msgstr "Punto final" +msgstr "Endpoint" #: mailu/ui/templates/fetch/list.html:22 msgid "Last check" @@ -437,7 +437,7 @@ msgstr "Añadir un gestor" #: mailu/ui/templates/manager/list.html:4 msgid "Manager list" -msgstr "Gestor de lista" +msgstr "Lista de gestores" #: mailu/ui/templates/manager/list.html:12 msgid "Add manager" @@ -578,7 +578,7 @@ msgstr "Lista de dominios externos (relayed)" #: mailu/ui/templates/relay/list.html:9 msgid "New relayed domain" -msgstr "Nuevo dominio externo (relayed)" +msgstr "Editar dominio externo (relay)" #: mailu/ui/forms.py:125 msgid "Your token (write it down, as it will never be displayed again)" diff --git a/core/admin/mailu/translations/eu/LC_MESSAGES/messages.po b/core/admin/mailu/translations/eu/LC_MESSAGES/messages.po new file mode 100644 index 00000000..6ca737a3 --- /dev/null +++ b/core/admin/mailu/translations/eu/LC_MESSAGES/messages.po @@ -0,0 +1,672 @@ +# Translations template for PROJECT. +# Copyright (C) 2018 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2018-04-22 12:10+0200\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" +"Last-Translator: Jaume Barber \n" +"Language-Team: Basque \n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.0.1\n" +"Generated-By: Babel 2.5.3\n" + +#: mailu/ui/forms.py:32 +msgid "Invalid email address." +msgstr "baliogabeko helbide elektronikoa." + +#: mailu/ui/forms.py:36 +msgid "Confirm" +msgstr "Ados" + +#: mailu/ui/forms.py:40 mailu/ui/forms.py:77 +msgid "E-mail" +msgstr "E-mail" + +#: mailu/ui/forms.py:41 mailu/ui/forms.py:78 mailu/ui/forms.py:90 +#: mailu/ui/forms.py:109 mailu/ui/forms.py:162 +#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:59 +msgid "Password" +msgstr "Pasahitza" + +#: mailu/ui/forms.py:42 mailu/ui/templates/login.html:4 +#: mailu/ui/templates/sidebar.html:111 +msgid "Sign in" +msgstr "" + +#: mailu/ui/forms.py:46 mailu/ui/forms.py:56 +#: mailu/ui/templates/domain/details.html:27 +#: mailu/ui/templates/domain/list.html:18 mailu/ui/templates/relay/list.html:17 +msgid "Domain name" +msgstr "" + +#: mailu/ui/forms.py:47 +msgid "Maximum user count" +msgstr "Erabiltzaileen gehieneko kopurua" + +#: mailu/ui/forms.py:48 +msgid "Maximum alias count" +msgstr "" + +#: mailu/ui/forms.py:49 +msgid "Maximum user quota" +msgstr "Erabiltzaile bakoitzeko gehieneko espazioa" + +#: mailu/ui/forms.py:50 +msgid "Enable sign-up" +msgstr "Gaitu erregistroa" + +#: mailu/ui/forms.py:51 mailu/ui/forms.py:72 mailu/ui/forms.py:83 +#: mailu/ui/forms.py:128 mailu/ui/forms.py:140 +#: mailu/ui/templates/alias/list.html:21 mailu/ui/templates/domain/list.html:21 +#: mailu/ui/templates/relay/list.html:19 mailu/ui/templates/token/list.html:19 +#: mailu/ui/templates/user/list.html:23 +msgid "Comment" +msgstr "Iruzkindua" + +#: mailu/ui/forms.py:52 mailu/ui/forms.py:61 mailu/ui/forms.py:66 +#: mailu/ui/forms.py:73 mailu/ui/forms.py:132 mailu/ui/forms.py:141 +msgid "Create" +msgstr "Sortu" + +#: mailu/ui/forms.py:57 +msgid "Initial admin" +msgstr "Administratzailea" + +#: mailu/ui/forms.py:58 +msgid "Admin password" +msgstr "Administratzaileko pasahitza" + +#: mailu/ui/forms.py:59 mailu/ui/forms.py:79 mailu/ui/forms.py:91 +msgid "Confirm password" +msgstr "Berretsi pasahitza" + +#: mailu/ui/forms.py:65 +msgid "Alternative name" +msgstr "Izen alternatiboa" + +#: mailu/ui/forms.py:70 +msgid "Relayed domain name" +msgstr "Igorritako domeinu izena" + +#: mailu/ui/forms.py:71 mailu/ui/templates/relay/list.html:18 +msgid "Remote host" +msgstr "Urruneko ostalaria" + +#: mailu/ui/forms.py:80 mailu/ui/templates/user/list.html:22 +#: mailu/ui/templates/user/signup_domain.html:16 +msgid "Quota" +msgstr "Espazioa" + +#: mailu/ui/forms.py:81 +msgid "Allow IMAP access" +msgstr "Baimendu IMAP sarbidea" + +#: mailu/ui/forms.py:82 +msgid "Allow POP3 access" +msgstr "Baimendu POP3 sarbidea" + +#: mailu/ui/forms.py:84 +msgid "Enabled" +msgstr "Gaituta" + +#: mailu/ui/forms.py:85 +msgid "Save" +msgstr "Gorde" + +#: mailu/ui/forms.py:89 +msgid "Email address" +msgstr "" + +#: mailu/ui/forms.py:93 mailu/ui/templates/sidebar.html:117 +#: mailu/ui/templates/user/signup.html:4 +#: mailu/ui/templates/user/signup_domain.html:4 +msgid "Sign up" +msgstr "Erregistratu" + +#: mailu/ui/forms.py:97 +msgid "Displayed name" +msgstr "" + +#: mailu/ui/forms.py:98 +msgid "Enable spam filter" +msgstr "Gaitu spam iragazkia" + +#: mailu/ui/forms.py:99 +msgid "Spam filter tolerance" +msgstr "Spam iragazkiaren tolerantzia" + +#: mailu/ui/forms.py:100 +msgid "Enable forwarding" +msgstr "Gaitu birbidaltzea" + +#: mailu/ui/forms.py:101 +msgid "Keep a copy of the emails" +msgstr "" + +#: mailu/ui/forms.py:103 mailu/ui/forms.py:139 +#: mailu/ui/templates/alias/list.html:20 +msgid "Destination" +msgstr "" + +#: mailu/ui/forms.py:105 +msgid "Save settings" +msgstr "Gorde ezarpenak" + +#: mailu/ui/forms.py:110 +msgid "Password check" +msgstr "" + +#: mailu/ui/forms.py:111 mailu/ui/templates/sidebar.html:16 +msgid "Update password" +msgstr "" + +#: mailu/ui/forms.py:115 +msgid "Enable automatic reply" +msgstr "" + +#: mailu/ui/forms.py:116 +msgid "Reply subject" +msgstr "" + +#: mailu/ui/forms.py:117 +msgid "Reply body" +msgstr "" + +#: mailu/ui/forms.py:119 +msgid "End of vacation" +msgstr "Oporren amaiera" + +#: mailu/ui/forms.py:120 +msgid "Update" +msgstr "Eguneratu" + +#: mailu/ui/forms.py:125 +msgid "Your token (write it down, as it will never be displayed again)" +msgstr "" + +#: mailu/ui/forms.py:130 mailu/ui/templates/token/list.html:20 +msgid "Authorized IP" +msgstr "Baimendutako IP" + +#: mailu/ui/forms.py:136 +msgid "Alias" +msgstr "Ezizenza" + +#: mailu/ui/forms.py:138 +msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" +msgstr "" + +#: mailu/ui/forms.py:145 +msgid "Admin email" +msgstr "" + +#: mailu/ui/forms.py:146 mailu/ui/forms.py:151 mailu/ui/forms.py:164 +msgid "Submit" +msgstr "" + +#: mailu/ui/forms.py:150 +msgid "Manager email" +msgstr "" + +#: mailu/ui/forms.py:155 +msgid "Protocol" +msgstr "" + +#: mailu/ui/forms.py:158 +msgid "Hostname or IP" +msgstr "" + +#: mailu/ui/forms.py:159 mailu/ui/templates/client.html:20 +#: mailu/ui/templates/client.html:47 +msgid "TCP port" +msgstr "TCP ataka" + +#: mailu/ui/forms.py:160 +msgid "Enable TLS" +msgstr "" + +#: mailu/ui/forms.py:161 mailu/ui/templates/client.html:28 +#: mailu/ui/templates/client.html:55 mailu/ui/templates/fetch/list.html:20 +msgid "Username" +msgstr "" + +#: mailu/ui/forms.py:163 +msgid "Keep emails on the server" +msgstr "" + +#: mailu/ui/forms.py:168 +msgid "Announcement subject" +msgstr "" + +#: mailu/ui/forms.py:170 +msgid "Announcement body" +msgstr "" + +#: mailu/ui/forms.py:172 +msgid "Send" +msgstr "" + +#: mailu/ui/templates/announcement.html:4 +msgid "Public announcement" +msgstr "" + +#: mailu/ui/templates/client.html:4 mailu/ui/templates/sidebar.html:82 +msgid "Client setup" +msgstr "" + +#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:43 +msgid "Mail protocol" +msgstr "" + +#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:51 +msgid "Server name" +msgstr "" + +#: mailu/ui/templates/confirm.html:4 +msgid "Confirm action" +msgstr "" + +#: mailu/ui/templates/confirm.html:13 +#, python-format +msgid "You are about to %(action)s. Please confirm your action." +msgstr "Zu zara %(action)s-etan. Mesedez ekintza honen berretsi." + +#: mailu/ui/templates/docker-error.html:4 +msgid "Docker error" +msgstr "Docker-en errorea" + +#: mailu/ui/templates/docker-error.html:12 +msgid "An error occurred while talking to the Docker server." +msgstr "" + +#: mailu/ui/templates/login.html:8 +msgid "to access the administration tools" +msgstr "" + +#: mailu/ui/templates/sidebar.html:11 mailu/ui/templates/user/list.html:34 +msgid "Settings" +msgstr "" + +#: mailu/ui/templates/sidebar.html:21 mailu/ui/templates/user/list.html:35 +msgid "Auto-reply" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:26 +#: mailu/ui/templates/user/list.html:36 +msgid "Fetched accounts" +msgstr "" + +#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/token/list.html:4 +msgid "Authentication tokens" +msgstr "" + +#: mailu/ui/templates/sidebar.html:35 +msgid "Administration" +msgstr "" + +#: mailu/ui/templates/sidebar.html:44 +msgid "Announcement" +msgstr "" + +#: mailu/ui/templates/sidebar.html:49 +msgid "Administrators" +msgstr "" + +#: mailu/ui/templates/sidebar.html:54 +msgid "Relayed domains" +msgstr "Igorritako domeinuak" + +#: mailu/ui/templates/sidebar.html:59 mailu/ui/templates/user/settings.html:15 +msgid "Antispam" +msgstr "Antispam" + +#: mailu/ui/templates/sidebar.html:66 +msgid "Mail domains" +msgstr "" + +#: mailu/ui/templates/sidebar.html:72 +msgid "Go to" +msgstr "" + +#: mailu/ui/templates/sidebar.html:76 +msgid "Webmail" +msgstr "" + +#: mailu/ui/templates/sidebar.html:87 +msgid "Website" +msgstr "" + +#: mailu/ui/templates/sidebar.html:92 +msgid "Help" +msgstr "" + +#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:98 +msgid "Register a domain" +msgstr "" + +#: mailu/ui/templates/sidebar.html:105 +msgid "Sign out" +msgstr "" + +#: mailu/ui/templates/working.html:4 +msgid "We are still working on this feature!" +msgstr "" + +#: mailu/ui/templates/admin/create.html:4 +msgid "Add a global administrator" +msgstr "" + +#: mailu/ui/templates/admin/list.html:4 +msgid "Global administrators" +msgstr "" + +#: mailu/ui/templates/admin/list.html:9 +msgid "Add administrator" +msgstr "" + +#: mailu/ui/templates/admin/list.html:16 mailu/ui/templates/alias/list.html:18 +#: mailu/ui/templates/alternative/list.html:18 +#: mailu/ui/templates/domain/list.html:16 mailu/ui/templates/fetch/list.html:18 +#: mailu/ui/templates/manager/list.html:18 +#: mailu/ui/templates/relay/list.html:16 mailu/ui/templates/token/list.html:18 +#: mailu/ui/templates/user/list.html:18 +msgid "Actions" +msgstr "" + +#: mailu/ui/templates/admin/list.html:17 mailu/ui/templates/alias/list.html:19 +#: mailu/ui/templates/manager/list.html:19 mailu/ui/templates/user/list.html:20 +msgid "Email" +msgstr "" + +#: mailu/ui/templates/admin/list.html:22 mailu/ui/templates/alias/list.html:29 +#: mailu/ui/templates/alternative/list.html:25 +#: mailu/ui/templates/domain/list.html:31 mailu/ui/templates/fetch/list.html:31 +#: mailu/ui/templates/manager/list.html:24 +#: mailu/ui/templates/relay/list.html:27 mailu/ui/templates/token/list.html:26 +#: mailu/ui/templates/user/list.html:31 +msgid "Delete" +msgstr "" + +#: mailu/ui/templates/alias/create.html:4 +msgid "Create alias" +msgstr "" + +#: mailu/ui/templates/alias/edit.html:4 +msgid "Edit alias" +msgstr "" + +#: mailu/ui/templates/alias/list.html:4 +msgid "Alias list" +msgstr "" + +#: mailu/ui/templates/alias/list.html:12 +msgid "Add alias" +msgstr "" + +#: mailu/ui/templates/alias/list.html:22 +#: mailu/ui/templates/alternative/list.html:20 +#: mailu/ui/templates/domain/list.html:22 mailu/ui/templates/fetch/list.html:24 +#: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:21 +#: mailu/ui/templates/user/list.html:24 +msgid "Created" +msgstr "" + +#: mailu/ui/templates/alias/list.html:23 mailu/ui/templates/domain/list.html:23 +#: mailu/ui/templates/fetch/list.html:25 mailu/ui/templates/relay/list.html:21 +#: mailu/ui/templates/user/list.html:25 +msgid "Last edit" +msgstr "" + +#: mailu/ui/templates/alias/list.html:28 mailu/ui/templates/domain/list.html:30 +#: mailu/ui/templates/fetch/list.html:30 mailu/ui/templates/relay/list.html:26 +#: mailu/ui/templates/user/list.html:30 +msgid "Edit" +msgstr "" + +#: mailu/ui/templates/alternative/create.html:4 +msgid "Create alternative domain" +msgstr "" + +#: mailu/ui/templates/alternative/list.html:4 +msgid "Alternative domain list" +msgstr "" + +#: mailu/ui/templates/alternative/list.html:12 +msgid "Add alternative" +msgstr "" + +#: mailu/ui/templates/alternative/list.html:19 +msgid "Name" +msgstr "" + +#: mailu/ui/templates/domain/create.html:4 +#: mailu/ui/templates/domain/list.html:9 +msgid "New domain" +msgstr "" + +#: mailu/ui/templates/domain/details.html:4 +msgid "Domain details" +msgstr "" + +#: mailu/ui/templates/domain/details.html:15 +msgid "Regenerate keys" +msgstr "" + +#: mailu/ui/templates/domain/details.html:17 +msgid "Generate keys" +msgstr "" + +#: mailu/ui/templates/domain/details.html:31 +msgid "DNS MX entry" +msgstr "" + +#: mailu/ui/templates/domain/details.html:35 +msgid "DNS SPF entries" +msgstr "" + +#: mailu/ui/templates/domain/details.html:42 +msgid "DKIM public key" +msgstr "" + +#: mailu/ui/templates/domain/details.html:46 +msgid "DNS DKIM entry" +msgstr "" + +#: mailu/ui/templates/domain/details.html:50 +msgid "DNS DMARC entry" +msgstr "" + +#: mailu/ui/templates/domain/edit.html:4 +msgid "Edit domain" +msgstr "" + +#: mailu/ui/templates/domain/list.html:4 +msgid "Domain list" +msgstr "" + +#: mailu/ui/templates/domain/list.html:17 +msgid "Manage" +msgstr "" + +#: mailu/ui/templates/domain/list.html:19 +msgid "Mailbox count" +msgstr "" + +#: mailu/ui/templates/domain/list.html:20 +msgid "Alias count" +msgstr "" + +#: mailu/ui/templates/domain/list.html:28 +msgid "Details" +msgstr "" + +#: mailu/ui/templates/domain/list.html:35 +msgid "Users" +msgstr "" + +#: mailu/ui/templates/domain/list.html:36 +msgid "Aliases" +msgstr "" + +#: mailu/ui/templates/domain/list.html:37 +msgid "Managers" +msgstr "" + +#: mailu/ui/templates/domain/list.html:39 +msgid "Alternatives" +msgstr "" + +#: mailu/ui/templates/domain/signup.html:13 +msgid "" +"In order to register a new domain, you must first setup the\n" +" domain zone so that the domain MX points to this server" +msgstr "" + +#: mailu/ui/templates/domain/signup.html:18 +msgid "" +"If you do not know how to setup an MX record for your DNS " +"zone,\n" +" please contact your DNS provider or administrator. Also, please wait " +"a\n" +" couple minutes after the MX is set so the local server " +"cache\n" +" expires." +msgstr "" + +#: mailu/ui/templates/fetch/create.html:4 +msgid "Add a fetched account" +msgstr "" + +#: mailu/ui/templates/fetch/edit.html:4 +msgid "Update a fetched account" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:12 +msgid "Add an account" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:19 +msgid "Endpoint" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:21 +msgid "Keep emails" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:22 +msgid "Last check" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:35 +msgid "yes" +msgstr "" + +#: mailu/ui/templates/fetch/list.html:35 +msgid "no" +msgstr "" + +#: mailu/ui/templates/manager/create.html:4 +msgid "Add a manager" +msgstr "" + +#: mailu/ui/templates/manager/list.html:4 +msgid "Manager list" +msgstr "" + +#: mailu/ui/templates/manager/list.html:12 +msgid "Add manager" +msgstr "" + +#: mailu/ui/templates/relay/create.html:4 +msgid "New relay domain" +msgstr "Igorritako domeinu berria" + +#: mailu/ui/templates/relay/edit.html:4 +msgid "Edit relayd domain" +msgstr "Editatu igorritako domeinua" + +#: mailu/ui/templates/relay/list.html:4 +msgid "Relayed domain list" +msgstr "Igorritako domeinuen zerrenda" + +#: mailu/ui/templates/relay/list.html:9 +msgid "New relayed domain" +msgstr "Igorritako domeinu berria" + +#: mailu/ui/templates/token/create.html:4 +msgid "Create an authentication token" +msgstr "" + +#: mailu/ui/templates/token/list.html:12 +msgid "New token" +msgstr "" + +#: mailu/ui/templates/user/create.html:4 +msgid "New user" +msgstr "" + +#: mailu/ui/templates/user/create.html:15 +msgid "General" +msgstr "" + +#: mailu/ui/templates/user/create.html:22 +msgid "Features and quotas" +msgstr "" + +#: mailu/ui/templates/user/edit.html:4 +msgid "Edit user" +msgstr "" + +#: mailu/ui/templates/user/forward.html:4 +msgid "Forward emails" +msgstr "" + +#: mailu/ui/templates/user/list.html:4 +msgid "User list" +msgstr "" + +#: mailu/ui/templates/user/list.html:12 +msgid "Add user" +msgstr "" + +#: mailu/ui/templates/user/list.html:19 mailu/ui/templates/user/settings.html:4 +msgid "User settings" +msgstr "" + +#: mailu/ui/templates/user/list.html:21 +msgid "Features" +msgstr "" + +#: mailu/ui/templates/user/password.html:4 +msgid "Password update" +msgstr "" + +#: mailu/ui/templates/user/reply.html:4 +msgid "Automatic reply" +msgstr "" + +#: mailu/ui/templates/user/settings.html:22 +msgid "Auto-forward" +msgstr "" + +#: mailu/ui/templates/user/signup_domain.html:8 +msgid "pick a domain for the new account" +msgstr "" + +#: mailu/ui/templates/user/signup_domain.html:14 +msgid "Domain" +msgstr "Domeinu izena" + +#: mailu/ui/templates/user/signup_domain.html:15 +msgid "Available slots" +msgstr "" diff --git a/core/admin/mailu/translations/it/LC_MESSAGES/messages.po b/core/admin/mailu/translations/it/LC_MESSAGES/messages.po index 9ef5ac84..9ed5e132 100644 --- a/core/admin/mailu/translations/it/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/it/LC_MESSAGES/messages.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Mailu\n" -"PO-Revision-Date: 2020-03-11 23:03+0000\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" "Last-Translator: Jaume Barber \n" "Language-Team: Italian \n" @@ -10,7 +10,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.11.2\n" +"X-Generator: Weblate 4.0.1\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." diff --git a/core/admin/mailu/translations/pt/LC_MESSAGES/messages.po b/core/admin/mailu/translations/pt/LC_MESSAGES/messages.po index 58338380..f9673767 100644 --- a/core/admin/mailu/translations/pt/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/pt/LC_MESSAGES/messages.po @@ -1,11 +1,16 @@ msgid "" msgstr "" +"Project-Id-Version: Mailu\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" +"Last-Translator: Jaume Barber \n" +"Language-Team: Portuguese \n" +"Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: POEditor.com\n" -"Project-Id-Version: Mailu\n" -"Language: pt\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.0.1\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." @@ -183,7 +188,7 @@ msgstr "Erro no docker" #: mailu/ui/templates/docker-error.html:12 msgid "An error occurred while talking to the Docker server." -msgstr "Um erro foi encontrado na conexão com o servidor Docker" +msgstr "Um erro foi encontrado na conexão com o servidor Docker." #: mailu/admin/templates/login.html:6 msgid "Your account" @@ -700,4 +705,3 @@ msgstr "Domínio" #: mailu/ui/templates/user/signup_domain.html:15 msgid "Available slots" msgstr "Slots disponíveis" - diff --git a/core/admin/mailu/translations/ru/LC_MESSAGES/messages.po b/core/admin/mailu/translations/ru/LC_MESSAGES/messages.po index 72e5f0cb..790119fc 100644 --- a/core/admin/mailu/translations/ru/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/ru/LC_MESSAGES/messages.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Mailu\n" -"PO-Revision-Date: 2019-07-22 06:23+0000\n" -"Last-Translator: kaiyou \n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" +"Last-Translator: Jaume Barber \n" "Language-Team: Russian \n" "Language: ru\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=" "4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 3.3\n" +"X-Generator: Weblate 4.0.1\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." @@ -189,7 +189,7 @@ msgstr "Ошибка Docker" #: mailu/ui/templates/docker-error.html:12 msgid "An error occurred while talking to the Docker server." -msgstr "Произошла ошибка при обращении к серверу Docker" +msgstr "Произошла ошибка при обращении к серверу Docker." #: mailu/admin/templates/login.html:6 msgid "Your account" diff --git a/core/admin/mailu/translations/sv/LC_MESSAGES/messages.po b/core/admin/mailu/translations/sv/LC_MESSAGES/messages.po index 825888f1..071040f6 100644 --- a/core/admin/mailu/translations/sv/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/sv/LC_MESSAGES/messages.po @@ -1,11 +1,16 @@ msgid "" msgstr "" +"Project-Id-Version: Mailu\n" +"PO-Revision-Date: 2021-03-04 18:46+0000\n" +"Last-Translator: Jaume Barber \n" +"Language-Team: Swedish \n" +"Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: POEditor.com\n" -"Project-Id-Version: Mailu\n" -"Language: sk\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.0.1\n" #: mailu/ui/forms.py:32 msgid "Invalid email address." @@ -183,7 +188,7 @@ msgstr "Docker fel" #: mailu/ui/templates/docker-error.html:12 msgid "An error occurred while talking to the Docker server." -msgstr "Ett fel inträffade vid kommunikation med Docker" +msgstr "Ett fel inträffade vid kommunikation med Docker." #: mailu/admin/templates/login.html:6 msgid "Your account" @@ -699,4 +704,3 @@ msgstr "" #: mailu/ui/templates/user/signup_domain.html:15 msgid "Available slots" msgstr "" - diff --git a/core/admin/mailu/ui/templates/base.html b/core/admin/mailu/ui/templates/base.html index 8a841e47..74d5653c 100644 --- a/core/admin/mailu/ui/templates/base.html +++ b/core/admin/mailu/ui/templates/base.html @@ -1,4 +1,5 @@ {% import "macros.html" as macros %} +{% import "bootstrap/utils.html" as utils %} @@ -37,6 +38,7 @@
+ {{ utils.flashed_messages(container=False) }} {% block content %}{% endblock %}
diff --git a/core/admin/mailu/ui/views/tokens.py b/core/admin/mailu/ui/views/tokens.py index 069587e1..820dd405 100644 --- a/core/admin/mailu/ui/views/tokens.py +++ b/core/admin/mailu/ui/views/tokens.py @@ -26,7 +26,7 @@ def token_create(user_email): form = forms.TokenForm() wtforms_components.read_only(form.displayed_password) if not form.raw_password.data: - form.raw_password.data = pwd.genword(entropy=128, charset="hex") + form.raw_password.data = pwd.genword(entropy=128, length=32, charset="hex") form.displayed_password.data = form.raw_password.data if form.validate_on_submit(): token = models.Token(user=user) diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index 8ad412cf..8968c38c 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -32,7 +32,7 @@ MarkupSafe==1.1.1 mysqlclient==1.4.2.post1 marshmallow==3.10.0 marshmallow-sqlalchemy==0.24.1 -passlib==1.7.1 +passlib==1.7.4 psycopg2==2.8.2 pycparser==2.19 pyOpenSSL==19.0.0 diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index df598c94..7a212ebe 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -79,6 +79,8 @@ http { listen [::]:443 ssl http2; include /etc/nginx/tls.conf; + ssl_stapling on; + ssl_stapling_verify on; ssl_session_cache shared:SSLHTTP:50m; add_header Strict-Transport-Security 'max-age=31536000'; @@ -215,7 +217,7 @@ mail { {% endif %} # Advertise real capabilites of backends (postfix/dovecot) - smtp_capabilities PIPELINING SIZE {{ MESSAGE_SIZE_LIMIT }} ETRN ENHANCEDSTATUSCODES 8BITMIME DSN CHUNKING; + smtp_capabilities PIPELINING SIZE {{ MESSAGE_SIZE_LIMIT }} ETRN ENHANCEDSTATUSCODES 8BITMIME DSN; pop3_capabilities TOP UIDL RESP-CODES PIPELINING AUTH-RESP-CODE USER; imap_capabilities IMAP4 IMAP4rev1 UIDPLUS SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+; diff --git a/docs/cli.rst b/docs/cli.rst index 891db152..10669108 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -87,7 +87,6 @@ where mail-config.yml looks like: - localpart: foo domain: example.com password_hash: klkjhumnzxcjkajahsdqweqqwr - hash_scheme: MD5-CRYPT aliases: - localpart: alias1 diff --git a/docs/compose/.env b/docs/compose/.env index 7f91c270..432b20b0 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -144,9 +144,8 @@ LOG_DRIVER=json-file # Docker-compose project name, this will prepended to containers names. COMPOSE_PROJECT_NAME=mailu -# Default password scheme used for newly created accounts and changed passwords -# (value: PBKDF2, BLF-CRYPT, SHA512-CRYPT, SHA256-CRYPT) -PASSWORD_SCHEME=PBKDF2 +# Number of rounds used by the password hashing scheme +CREDENTIAL_ROUNDS=12 # Header to take the real ip from REAL_IP_HEADER= diff --git a/docs/configuration.rst b/docs/configuration.rst index 5ff3546a..26bdb024 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -138,9 +138,9 @@ Depending on your particular deployment you most probably will want to change th Advanced settings ----------------- -The ``PASSWORD_SCHEME`` is the password encryption scheme. You should use the -default value, unless you are importing password from a separate system and -want to keep using the old password encryption scheme. +The ``CREDENTIAL_ROUNDS`` (default: 12) setting is the number of rounds used by the password hashing scheme. The number of rounds can be reduced in case faster authentication is needed or increased when additional protection is desired. Keep in mind that this is a mitigation against offline attacks on password hashes, aiming to prevent credential stuffing (due to password re-use) on other systems. + +The ``SESSION_COOKIE_SECURE`` (default: True) setting controls the secure flag on the cookies of the administrative interface. It should only be turned off if you intend to access it over plain HTTP. The ``LOG_LEVEL`` setting is used by the python start-up scripts as a logging threshold. Log messages equal or higher than this priority will be printed. diff --git a/docs/faq.rst b/docs/faq.rst index b292cd05..59a841dc 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -129,7 +129,7 @@ So when you have something like this: - The admin interface generates ``MX`` and ``SPF`` examples which point to the first entry of ``HOSTNAMES`` but these are only examples. You can modify them to use any other ``HOSTNAMES`` entry. -You're mail service will be reachable for IMAP, POP3, SMTP and Webmail at the addresses: +Your mail service will be reachable for IMAP, POP3, SMTP and Webmail at the addresses: - mail.example.com - mail.foo.com @@ -257,7 +257,10 @@ Postfix, Dovecot, Nginx and Rspamd support overriding configuration files. Overr ``$ROOT/overrides``. Please refer to the official documentation of those programs for the correct syntax. The following file names will be taken as override configuration: -- `Postfix`_ - ``postfix.cf`` in postfix sub-directory; +- `Postfix`_ : + - ``main.cf`` as ``$ROOT/overrides/postfix/postfix.cf`` + - ``master.cf`` as ``$ROOT/overrides/postfix/postfix.master`` + - All ``$ROOT/overrides/postfix/*.map`` files - `Dovecot`_ - ``dovecot.conf`` in dovecot sub-directory; - `Nginx`_ - All ``*.conf`` files in the ``nginx`` sub-directory; - `Rspamd`_ - All files in the ``rspamd`` sub-directory. diff --git a/docs/swarm/master/README.md b/docs/swarm/master/README.md index 58723c33..42e742da 100644 --- a/docs/swarm/master/README.md +++ b/docs/swarm/master/README.md @@ -106,6 +106,9 @@ As a side effect of this ingress mode "feature", make sure that the ingress subn - front and webmail are scalable (pending POD_ADDRESS_RANGE is used), although the let's encrypt magic might not like it (race condidtion ? or risk to be banned by let's encrypt server if too many front containers attemps to renew the certs at the same time) - redis, antispam, antivirus, fetchmail, admin, webdav have not been tested (hence replicas=1 in the following docker-compose.yml file) +## Docker secrets +There are DB_PW_FILE and SECRET_KEY_FILE environment variables available to specify files for these variables. These can be used to configure Docker secrets instead of writing the values directly into the `docker-compose.yml` or `mailu.env`. + ## Variable substitution and docker-compose.yml The docker stack deploy command doesn't support variable substitution in the .yml file itself. As a consequence, we cannot simply use ``` docker stack deploy -c docker.compose.yml mailu ``` diff --git a/tests/compose/core/00_create_users.sh b/tests/compose/core/00_create_users.sh index 49d0511b..f5108302 100755 --- a/tests/compose/core/00_create_users.sh +++ b/tests/compose/core/00_create_users.sh @@ -6,6 +6,6 @@ echo "The above error was intended!" docker-compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu admin admin mailu.io 'FooBar' --mode=ifmissing || exit 1 # Should not fail and update the password; update mode docker-compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu admin admin mailu.io 'password' --mode=update || exit 1 -docker-compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user user mailu.io 'password' 'SHA512-CRYPT' || exit 1 -docker-compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user 'user/with/slash' mailu.io 'password' 'SHA512-CRYPT' || exit 1 +docker-compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user user mailu.io 'password' || exit 1 +docker-compose -f tests/compose/core/docker-compose.yml exec -T admin flask mailu user 'user/with/slash' mailu.io 'password' || exit 1 echo "User testing succesfull!" diff --git a/tests/compose/core/02_forward_test.sh b/tests/compose/core/02_forward_test.sh index 595820cf..a53fa459 100755 --- a/tests/compose/core/02_forward_test.sh +++ b/tests/compose/core/02_forward_test.sh @@ -2,7 +2,6 @@ cat << EOF | docker-compose -f tests/compose/core/docker-compose.yml exec -T adm users: - localpart: forwardinguser password_hash: "\$1\$F2OStvi1\$Q8hBIHkdJpJkJn/TrMIZ9/" - hash_scheme: MD5-CRYPT domain: mailu.io forward_enabled: true forward_destination: ["user@mailu.io"] @@ -14,7 +13,6 @@ cat << EOF | docker-compose -f tests/compose/core/docker-compose.yml exec -T adm users: - localpart: forwardinguser password_hash: "\$1\$F2OStvi1\$Q8hBIHkdJpJkJn/TrMIZ9/" - hash_scheme: MD5-CRYPT domain: mailu.io forward_enabled: false forward_destination: [] diff --git a/tests/compose/core/04_reply_test.sh b/tests/compose/core/04_reply_test.sh index 83c114f6..e1479cf0 100755 --- a/tests/compose/core/04_reply_test.sh +++ b/tests/compose/core/04_reply_test.sh @@ -2,7 +2,6 @@ cat << EOF | docker-compose -f tests/compose/core/docker-compose.yml exec -T adm users: - localpart: replyuser password_hash: "\$1\$F2OStvi1\$Q8hBIHkdJpJkJn/TrMIZ9/" - hash_scheme: MD5-CRYPT domain: mailu.io reply_enabled: true reply_subject: This will not reach me @@ -15,7 +14,6 @@ cat << EOF | docker-compose -f tests/compose/core/docker-compose.yml exec -T adm users: - localpart: replyuser password_hash: "\$1\$F2OStvi1\$Q8hBIHkdJpJkJn/TrMIZ9/" - hash_scheme: MD5-CRYPT domain: mailu.io reply_enabled: false EOF diff --git a/towncrier/newsfragments/1607.feature b/towncrier/newsfragments/1607.feature new file mode 100644 index 00000000..de9f0895 --- /dev/null +++ b/towncrier/newsfragments/1607.feature @@ -0,0 +1 @@ +Implement SECRET_KEY_FILE and DB_PW_FILE variables for usage with Docker secrets. diff --git a/towncrier/newsfragments/1618.feature b/towncrier/newsfragments/1618.feature new file mode 100644 index 00000000..443f2b5c --- /dev/null +++ b/towncrier/newsfragments/1618.feature @@ -0,0 +1 @@ +Enable OCSP stapling for the http server within nginx. diff --git a/towncrier/newsfragments/1662.feature b/towncrier/newsfragments/1662.feature new file mode 100644 index 00000000..f8219757 --- /dev/null +++ b/towncrier/newsfragments/1662.feature @@ -0,0 +1 @@ +Enable support of all hash types passlib supports. diff --git a/towncrier/newsfragments/1712.misc b/towncrier/newsfragments/1712.misc new file mode 100644 index 00000000..57c5a3b8 --- /dev/null +++ b/towncrier/newsfragments/1712.misc @@ -0,0 +1 @@ +This adds more details about the postfix-override possibilities (fixes #1628) diff --git a/towncrier/newsfragments/1753.feature b/towncrier/newsfragments/1753.feature new file mode 100644 index 00000000..09eb834a --- /dev/null +++ b/towncrier/newsfragments/1753.feature @@ -0,0 +1 @@ +Switch to bcrypt_sha256, replace PASSWORD_SCHEME with CREDENTIAL_ROUNDS and dynamically update existing hashes on first login diff --git a/webmails/rainloop/Dockerfile b/webmails/rainloop/Dockerfile index c67c7496..ee040f25 100644 --- a/webmails/rainloop/Dockerfile +++ b/webmails/rainloop/Dockerfile @@ -2,10 +2,10 @@ ARG ARCH="" ARG QEMU=other # NOTE: only add file if building for arm -FROM ${ARCH}php:7.3-apache as build_arm +FROM ${ARCH}php:7.4-apache as build_arm ONBUILD COPY --from=balenalib/rpi-alpine:3.10 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static -FROM ${ARCH}php:7.3-apache as build_other +FROM ${ARCH}php:7.4-apache as build_other FROM build_${QEMU} #Shared layer between rainloop and roundcube diff --git a/webmails/roundcube/Dockerfile b/webmails/roundcube/Dockerfile index 79b911b0..1c303342 100644 --- a/webmails/roundcube/Dockerfile +++ b/webmails/roundcube/Dockerfile @@ -1,10 +1,10 @@ # NOTE: only add file if building for arm ARG ARCH="" ARG QEMU=other -FROM ${ARCH}php:7.3-apache as build_arm +FROM ${ARCH}php:7.4-apache as build_arm ONBUILD COPY --from=balenalib/rpi-alpine:3.10 /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static -FROM ${ARCH}php:7.3-apache as build_other +FROM ${ARCH}php:7.4-apache as build_other FROM build_${QEMU} #Shared layer between rainloop and roundcube diff --git a/webmails/roundcube/config.inc.php b/webmails/roundcube/config.inc.php index eb40047a..627b96a7 100644 --- a/webmails/roundcube/config.inc.php +++ b/webmails/roundcube/config.inc.php @@ -5,7 +5,7 @@ $config = array(); // Generals $config['db_dsnw'] = getenv('DB_DSNW');; $config['temp_dir'] = '/tmp/'; -$config['des_key'] = getenv('SECRET_KEY'); +$config['des_key'] = getenv('SECRET_KEY') ? getenv('SECRET_KEY') : trim(file_get_contents(getenv('SECRET_KEY_FILE'))); $config['cipher_method'] = 'AES-256-CBC'; $config['identities_level'] = 0; $config['reply_all_mode'] = 1;