mirror of
https://github.com/outbackdingo/Mailu.git
synced 2026-01-27 18:19:36 +00:00
Merge branch 'master' into bf/2856
This commit is contained in:
2
.github/workflows/build_test_deploy.yml
vendored
2
.github/workflows/build_test_deploy.yml
vendored
@@ -474,7 +474,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ["setup", "docs", "fetchmail", "webmail", "admin", "traefik-certdumper", "radicale", "clamav", "rspamd", "oletools", "postfix", "dovecot", "unbound", "nginx"]
|
||||
target: ["setup", "docs", "fetchmail", "webmail", "admin", "traefik-certdumper", "radicale", "rspamd", "oletools", "postfix", "dovecot", "unbound", "nginx"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Retrieve global variables
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ pip-selfcheck.json
|
||||
/core/admin/lib*
|
||||
/core/admin/bin
|
||||
/core/admin/include
|
||||
/core/base/.venv
|
||||
/docs/lib*
|
||||
/docs/bin
|
||||
/docs/include
|
||||
|
||||
@@ -22,6 +22,8 @@ Other contributors:
|
||||
- "SunMar" - Dutch translation
|
||||
- "Marty Hou" - Chinese Simple translation
|
||||
- [Thomas Sänger](https://github.com/HorayNarea) - German translation
|
||||
- [Danylo Sydorenko](https://github.com/Prosta4okua) - Ukrainian translation
|
||||
- [Hossein Hosni](https://github.com/hosni) - [Contributions](https://github.com/Mailu/Mailu/commits?author=hosni)
|
||||
- [Tim Mohlmann](https://github.com/muhlemmer) - [Contributions](https://github.com/Mailu/Mailu/commits?author=muhlemmer)
|
||||
- [Ionut Filip](https://github.com/ionutfilip) - [Contributions](https://github.com/Mailu/Mailu/commits?author=ionutfilip)
|
||||
- [Ichikawa Yuriko](https://github.com/IchikawaYukko) - [Contributions](https://github.com/Mailu/Mailu/commits?author=IchikawaYukko) Japanese translation
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<!--
|
||||
|
||||
Thank you for opening an issue with Mailu. Please understand that issues are meant for bugs and enhancement-requests.
|
||||
For **user-support questions**, reach out to us on [matrix](https://matrix.to/#/#mailu:tedomum.net).
|
||||
Thank you for opening an issue with Mailu. Please understand that issues are meant for bugs only. The bug report should follow the issue template and provide clear replication steps and logs.
|
||||
For **user-support questions**, reach out to us on [matrix](https://matrix.to/#/#mailu:tedomum.net) or [disussions](https://github.com/Mailu/Mailu/discussions/categories/user-support).
|
||||
|
||||
For anything but bug reports use the [matrix channel](https://matrix.to/#/#mailu:tedomum.net) or [disussions](https://github.com/Mailu/Mailu/discussions).
|
||||
So use discussions for topics such as
|
||||
|
||||
* Checking announcements.
|
||||
* General discussion about Mailu usage or using Mail software in general.
|
||||
* Feature requests
|
||||
* User support.
|
||||
|
||||
To be able to help you best, we need some more information.
|
||||
|
||||
@@ -10,21 +18,17 @@ Before you open your issue
|
||||
- Check [documentation](https://mailu.io/master/) and [FAQ](https://mailu.io/master/faq.html). (Tip, use the search function on the documentation page)
|
||||
- You understand `Mailu` is made by volunteers in their **free time** — be concise, civil and accept that delays can occur.
|
||||
- The title of the issue should be short and simple. It should contain specific terms related to the actual issue. Be specific while writing the title.
|
||||
- You understand issues are only meant for bug reports that follow the issue template. Non bug reports or bug reports that do not follow the template will be moved to [disussions](https://github.com/Mailu/Mailu/discussions)
|
||||
|
||||
Please put your text outside of the comment blocks to be visible. You can use the button "Preview" above to check.
|
||||
|
||||
If you do not follow the issue template suggested below your issue may be summarily closed.
|
||||
|
||||
-->
|
||||
|
||||
## Environment & Version
|
||||
|
||||
### Environment
|
||||
|
||||
- [ ] docker compose
|
||||
- [ ] kubernetes
|
||||
- [ ] docker swarm
|
||||
|
||||
### Version
|
||||
|
||||
- `docker compose version`
|
||||
- Version: `master`
|
||||
|
||||
<!--
|
||||
@@ -35,6 +39,11 @@ $> docker ps -a | grep mailu
|
||||
$> grep MAILU_VERSION docker-compose.yml mailu.env
|
||||
-->
|
||||
|
||||
If you are not using docker compose do not file any new issue here.
|
||||
Kubernetes related issues belong to https://github.com/Mailu/helm-charts/issues
|
||||
If you are not using docker compose or kubernetes, create a new thread on user support in [disussions](https://github.com/Mailu/Mailu/discussions/categories/user-support).
|
||||
Non-bug reports (or bug reports that do not follow the template) are moved to [disussions](https://github.com/Mailu/Mailu/discussions).
|
||||
|
||||
## Description
|
||||
<!--
|
||||
Further explain the bug in a few words. It should be clear what the unexpected behaviour is. Share it in an easy-to-understand language.
|
||||
@@ -63,9 +72,10 @@ You can get the logs via `docker logs <container name> --tail 1000`.
|
||||
For example for the admin container: `docker logs mailu_admin_1 --tail 1000`
|
||||
or using docker compose `docker compose -f /mailu/docker-compose.yml logs --tail 1000 admin`
|
||||
|
||||
If you can find the relevant section, please share only the parts that seem relevant. If you have any logs, please enclose them in code tags, like so:
|
||||
If you can find the relevant section, please share only the parts that seem relevant. If you have any logs, please enclose them in code tags and in a section, like so:
|
||||
|
||||
```
|
||||
Your logs here!
|
||||
```
|
||||
|
||||
-->
|
||||
|
||||
@@ -17,8 +17,8 @@ Features
|
||||
|
||||
Main features include:
|
||||
|
||||
- **Standard email server**, IMAP and IMAP+, SMTP and Submission with autoconfiguration profiles for clients
|
||||
- **Advanced email features**, aliases, domain aliases, custom routing
|
||||
- **Standard email server**, IMAP and IMAP+, SMTP and Submission with auto-configuration profiles for clients
|
||||
- **Advanced email features**, aliases, domain aliases, custom routing, full-text search of email attachments
|
||||
- **Web access**, multiple Webmails and administration interface
|
||||
- **User features**, aliases, auto-reply, auto-forward, fetched accounts, managesieve
|
||||
- **Admin features**, global admins, announcements, per-domain delegation, quotas
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN set -euxo pipefail \
|
||||
; npm install --no-audit --no-fund \
|
||||
; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \
|
||||
; mkdir assets \
|
||||
; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE zh; do \
|
||||
; for l in ca da de:de-DE en:en-GB es:es-ES eu fa fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE uk zh zh_TW:zh-HANT; do \
|
||||
cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \
|
||||
done
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import hmac
|
||||
|
||||
class NoPingFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
if (record.args['{host}i'] == 'localhost' and record.args['r'] == 'GET /ping HTTP/1.1'):
|
||||
if record.args['r'].endswith(' /ping HTTP/1.1'):
|
||||
return False
|
||||
if record.args['r'].endswith(' /internal/rspamd/local_domains HTTP/1.1'):
|
||||
return False
|
||||
@@ -40,7 +40,7 @@ def create_app_from_config(config):
|
||||
models.db.init_app(app)
|
||||
utils.session.init_app(app)
|
||||
utils.limiter.init_app(app)
|
||||
utils.babel.init_app(app)
|
||||
utils.babel.init_app(app, locale_selector=utils.get_locale)
|
||||
utils.login.init_app(app)
|
||||
utils.login.user_loader(models.User.get)
|
||||
utils.proxy.init_app(app)
|
||||
@@ -52,13 +52,14 @@ def create_app_from_config(config):
|
||||
app.truncated_pw_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('TRUNCATED_PW_KEY', 'utf-8'), 'sha256').digest()
|
||||
|
||||
# Initialize list of translations
|
||||
app.config.translations = {
|
||||
str(locale): locale
|
||||
for locale in sorted(
|
||||
utils.babel.list_translations(),
|
||||
key=lambda l: l.get_language_name().title()
|
||||
)
|
||||
}
|
||||
with app.app_context():
|
||||
app.config.translations = {
|
||||
str(locale): locale
|
||||
for locale in sorted(
|
||||
utils.babel.list_translations(),
|
||||
key=lambda l: l.get_language_name().title()
|
||||
)
|
||||
}
|
||||
|
||||
# Initialize debugging tools
|
||||
if app.config.get("DEBUG"):
|
||||
|
||||
@@ -37,7 +37,8 @@ error_fields = api.model('Error', {
|
||||
'message': fields.String,
|
||||
})
|
||||
|
||||
from . import domains
|
||||
from . import domain
|
||||
from . import alias
|
||||
from . import relay
|
||||
from . import user
|
||||
from . import token
|
||||
|
||||
@@ -115,13 +115,13 @@ class Domains(Resource):
|
||||
if 'comment' in data:
|
||||
domain_new.comment = data['comment']
|
||||
if 'max_users' in data:
|
||||
domain_new.comment = data['max_users']
|
||||
domain_new.max_users = data['max_users']
|
||||
if 'max_aliases' in data:
|
||||
domain_new.comment = data['max_aliases']
|
||||
domain_new.max_aliases = data['max_aliases']
|
||||
if 'max_quota_bytes' in data:
|
||||
domain_new.comment = data['max_quota_bytes']
|
||||
domain_new.max_quota_bytes = data['max_quota_bytes']
|
||||
if 'signup_enabled' in data:
|
||||
domain_new.comment = data['signup_enabled']
|
||||
domain_new.signup_enabled = data['signup_enabled']
|
||||
models.db.session.add(domain_new)
|
||||
#apply the changes
|
||||
db.session.commit()
|
||||
@@ -177,13 +177,13 @@ class Domain(Resource):
|
||||
if 'comment' in data:
|
||||
domain_found.comment = data['comment']
|
||||
if 'max_users' in data:
|
||||
domain_found.comment = data['max_users']
|
||||
domain_found.max_users = data['max_users']
|
||||
if 'max_aliases' in data:
|
||||
domain_found.comment = data['max_aliases']
|
||||
domain_found.max_aliases = data['max_aliases']
|
||||
if 'max_quota_bytes' in data:
|
||||
domain_found.comment = data['max_quota_bytes']
|
||||
domain_found.max_quota_bytes = data['max_quota_bytes']
|
||||
if 'signup_enabled' in data:
|
||||
domain_found.comment = data['signup_enabled']
|
||||
domain_found.signup_enabled = data['signup_enabled']
|
||||
models.db.session.add(domain_found)
|
||||
|
||||
#apply the changes
|
||||
170
core/admin/mailu/api/v1/token.py
Normal file
170
core/admin/mailu/api/v1/token.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from flask_restx import Resource, fields, marshal
|
||||
import validators, datetime
|
||||
import flask
|
||||
from passlib import pwd
|
||||
|
||||
from . import api, response_fields
|
||||
from .. import common
|
||||
from ... import models
|
||||
|
||||
db = models.db
|
||||
|
||||
token = api.namespace('token', description='Token operations')
|
||||
|
||||
token_user_fields = api.model('TokenGetResponse', {
|
||||
'id': fields.String(description='The record id of the token (unique identifier)', example='1'),
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'),
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at'),
|
||||
'Last edit': fields.String(description='The date when the token was last modifified', example='John.Doe@example.com', attribute='updated_at')
|
||||
})
|
||||
|
||||
token_user_fields_post = api.model('TokenPost', {
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'),
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
})
|
||||
|
||||
token_user_fields_post2 = api.model('TokenPost2', {
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
})
|
||||
|
||||
token_user_post_response = api.model('TokenPostResponse', {
|
||||
'id': fields.String(description='The record id of the token (unique identifier)', example='1'),
|
||||
'token': fields.String(description='The created authentication token for the user.', example='2caf6607de5129e4748a2c061aee56f2', attribute='password'),
|
||||
'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'),
|
||||
'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'),
|
||||
'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'),
|
||||
'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at')
|
||||
})
|
||||
|
||||
@token.route('')
|
||||
class Tokens(Resource):
|
||||
@token.doc('list_tokens')
|
||||
@token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
"""List tokens"""
|
||||
return models.Token.query.all()
|
||||
|
||||
@token.doc('create_token')
|
||||
@token.expect(token_user_fields_post)
|
||||
@token.response(200, 'Success', token_user_post_response)
|
||||
@token.response(400, 'Input validation exception')
|
||||
@token.response(409, 'Duplicate relay', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create a new token"""
|
||||
data = api.payload
|
||||
email = data['email']
|
||||
if not validators.email(email):
|
||||
return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400
|
||||
user_found = models.User.query.get(email)
|
||||
if not user_found:
|
||||
return {'code': 404, 'message': f'User {email} cannot be found'}, 404
|
||||
tokens = user_found.tokens
|
||||
|
||||
token_new = models.Token(user_email=data['email'])
|
||||
if 'comment' in data:
|
||||
token_new.comment = data['comment']
|
||||
if 'AuthorizedIP' in data:
|
||||
token_new.ip = data['AuthorizedIP'].replace(' ','').split(',')
|
||||
raw_password = pwd.genword(entropy=128, length=32, charset="hex")
|
||||
token_new.set_password(raw_password)
|
||||
models.db.session.add(token_new)
|
||||
#apply the changes
|
||||
db.session.commit()
|
||||
response_dict = {
|
||||
'id' : token_new.id,
|
||||
'token' : raw_password,
|
||||
'email' : token_new.user_email,
|
||||
'comment' : token_new.comment,
|
||||
'AuthorizedIP' : token_new.ip,
|
||||
'Created': str(token_new.created_at),
|
||||
}
|
||||
|
||||
return response_dict
|
||||
|
||||
@token.route('user/<string:email>')
|
||||
class Token(Resource):
|
||||
@token.doc('find_tokens_of_user')
|
||||
@token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, email):
|
||||
"Find tokens of user"
|
||||
if not validators.email(email):
|
||||
return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400
|
||||
user_found = models.User.query.get(email)
|
||||
if not user_found:
|
||||
return {'code': 404, 'message': f'User {email} cannot be found'}, 404
|
||||
tokens = user_found.tokens
|
||||
return tokens
|
||||
|
||||
@token.doc('create_token')
|
||||
@token.expect(token_user_fields_post2)
|
||||
@token.response(200, 'Success', token_user_post_response)
|
||||
@token.response(400, 'Input validation exception')
|
||||
@token.response(409, 'Duplicate relay', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self, email):
|
||||
""" Create a new token for user"""
|
||||
data = api.payload
|
||||
if not validators.email(email):
|
||||
return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400
|
||||
user_found = models.User.query.get(email)
|
||||
if not user_found:
|
||||
return {'code': 404, 'message': f'User {email} cannot be found'}, 404
|
||||
|
||||
token_new = models.Token(user_email=email)
|
||||
if 'comment' in data:
|
||||
token_new.comment = data['comment']
|
||||
if 'AuthorizedIP' in data:
|
||||
token_new.ip = token_new.ip = data['AuthorizedIP'].replace(' ','').split(',')
|
||||
raw_password = pwd.genword(entropy=128, length=32, charset="hex")
|
||||
token_new.set_password(raw_password)
|
||||
models.db.session.add(token_new)
|
||||
#apply the changes
|
||||
db.session.commit()
|
||||
response_dict = {
|
||||
'id' : token_new.id,
|
||||
'token' : raw_password,
|
||||
'email' : token_new.user_email,
|
||||
'comment' : token_new.comment,
|
||||
'AuthorizedIP' : token_new.ip,
|
||||
'Created': str(token_new.created_at),
|
||||
}
|
||||
return response_dict
|
||||
|
||||
@token.route('/<string:token_id>')
|
||||
class Token(Resource):
|
||||
@token.doc('find_token')
|
||||
@token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, token_id):
|
||||
"Find token"
|
||||
token = models.Token.query.get(token_id)
|
||||
if not token:
|
||||
return { 'code' : 404, 'message' : f'Record cannot be found for id {token_id} or invalid id provided'}, 404
|
||||
return token
|
||||
|
||||
@token.doc('delete_token')
|
||||
@token.response(200, 'Success', response_fields)
|
||||
@token.response(400, 'Input validation exception', response_fields)
|
||||
@token.response(404, 'Token not found', response_fields)
|
||||
@token.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, token_id):
|
||||
""" Delete token """
|
||||
token = models.Token.query.get(token_id)
|
||||
if not token:
|
||||
return { 'code' : 404, 'message' : f'Record cannot be found for id {token_id} or invalid id provided'}, 404
|
||||
db.session.delete(token)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Token with id {token_id} has been deleted'}, 200
|
||||
@@ -14,6 +14,7 @@ user_fields_get = api.model('UserGet', {
|
||||
'password': fields.String(description="Hash of the user's password; Example='$bcrypt-sha256$v=2,t=2b,r=12$fmsAdJbYAD1gGQIE5nfJq.$zLkQUEs2XZfTpAEpcix/1k5UTNPm0jO'"),
|
||||
'comment': fields.String(description='A description for the user. This description is shown on the Users page', example='my comment'),
|
||||
'quota_bytes': fields.Integer(description='The maximum quota for the user’s email box in bytes', example='1000000000'),
|
||||
'quota_bytes_used': fields.Integer(description='The size of the user’s email box in bytes', example='5000000'),
|
||||
'global_admin': fields.Boolean(description='Make the user a global administrator'),
|
||||
'enabled': fields.Boolean(description='Enable the user. When an user is disabled, the user is unable to login to the Admin GUI or webmail or access his email via IMAP/POP3 or send mail'),
|
||||
'change_pw_next_login': fields.Boolean(description='Force the user to change their password at next login'),
|
||||
|
||||
@@ -72,9 +72,12 @@ DEFAULT_CONFIG = {
|
||||
'LOGO_URL': None,
|
||||
'LOGO_BACKGROUND': None,
|
||||
# Advanced settings
|
||||
'AUTH_REQUIRE_TOKENS': False,
|
||||
'API': False,
|
||||
'WEB_API': '/api',
|
||||
'API_TOKEN': None,
|
||||
'FULL_TEXT_SEARCH': 'en',
|
||||
'FULL_TEXT_SEARCH_ATTACHMENTS': False,
|
||||
'LOG_LEVEL': 'INFO',
|
||||
'SESSION_KEY_BITS': 128,
|
||||
'SESSION_TIMEOUT': 3600,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import flask_debugtoolbar
|
||||
#Note: Currently flask_debugtoolbar is not compatible with flask.
|
||||
#import flask_debugtoolbar
|
||||
|
||||
from werkzeug.middleware.profiler import ProfilerMiddleware
|
||||
|
||||
|
||||
# Debugging toolbar
|
||||
toolbar = flask_debugtoolbar.DebugToolbarExtension()
|
||||
#toolbar = flask_debugtoolbar.DebugToolbarExtension()
|
||||
|
||||
|
||||
# Profiler
|
||||
|
||||
@@ -50,8 +50,12 @@ def check_credentials(user, password, ip, protocol=None, auth_port=None, source_
|
||||
app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: badip: token-{token.id}: {token.comment or ""!r}')
|
||||
return False # we can return directly here since the token is valid
|
||||
if user.check_password(password):
|
||||
app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: success: password')
|
||||
return True
|
||||
if app.config['AUTH_REQUIRE_TOKENS'] and not protocol in ['web', 'sso']:
|
||||
app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: password ok, but a token is required')
|
||||
return False
|
||||
else:
|
||||
app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: success: password')
|
||||
return True
|
||||
app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: badauth: {utils.truncated_pw_hash(password)}')
|
||||
return False
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@ if spamtest :percent :value "gt" :comparator "i;ascii-numeric" "{{ user.spam_thr
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
if exists "X-Virus" {
|
||||
discard;
|
||||
stop;
|
||||
}
|
||||
|
||||
{% if user.reply_active %}
|
||||
if not address :localpart :contains ["From","Reply-To"] ["noreply","no-reply"]{
|
||||
vacation :days 1 {% if user.displayed_name != "" %}:from "{{ user.displayed_name }} <{{ user.email }}>"{% endif %} :subject "{{ user.reply_subject }}" "{{ user.reply_body }}";
|
||||
|
||||
@@ -232,9 +232,7 @@ class Domain(Base):
|
||||
""" return DKIM record for domain """
|
||||
if self.dkim_key:
|
||||
selector = app.config['DKIM_SELECTOR']
|
||||
txt = f'v=DKIM1; k=rsa; p={self.dkim_publickey}'
|
||||
record = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250))
|
||||
return f'{selector}._domainkey.{self.name}. 600 IN TXT {record}'
|
||||
return f'{selector}._domainkey.{self.name}. 600 IN TXT "v=DKIM1; k=rsa; p={self.dkim_publickey}"'
|
||||
|
||||
@cached_property
|
||||
def dns_dmarc(self):
|
||||
|
||||
@@ -511,6 +511,10 @@ msgstr ""
|
||||
msgid "Generate keys"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:19
|
||||
msgid "Download zonefile"
|
||||
msgstr ""
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:30
|
||||
msgid "DNS MX entry"
|
||||
msgstr ""
|
||||
|
||||
@@ -53,7 +53,7 @@ msgstr "Configuración del cliente"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114
|
||||
msgid "Website"
|
||||
msgstr "Correo web"
|
||||
msgstr "Sitio web"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120
|
||||
msgid "Help"
|
||||
@@ -173,7 +173,7 @@ msgstr "Habilitar filtro de spam"
|
||||
|
||||
#: mailu/ui/forms.py:103
|
||||
msgid "Enable marking spam mails as read"
|
||||
msgstr ""
|
||||
msgstr "Habilitar marcado de correos spam como leídos"
|
||||
|
||||
#: mailu/ui/forms.py:104
|
||||
msgid "Spam filter tolerance"
|
||||
@@ -218,7 +218,7 @@ msgstr "Texto de la respuesta"
|
||||
|
||||
#: mailu/ui/forms.py:122
|
||||
msgid "Start of vacation"
|
||||
msgstr ""
|
||||
msgstr "Comienzo de las vacaciones"
|
||||
|
||||
#: mailu/ui/forms.py:123
|
||||
msgid "End of vacation"
|
||||
@@ -346,7 +346,7 @@ msgstr "Ocurrió un error en la comunicación con el servidor Docker."
|
||||
|
||||
#: mailu/ui/templates/macros.html:129
|
||||
msgid "copy to clipboard"
|
||||
msgstr ""
|
||||
msgstr "copiar en portapapeles"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:15
|
||||
msgid "My account"
|
||||
|
||||
731
core/admin/mailu/translations/fa/LC_MESSAGES/messages.po
Normal file
731
core/admin/mailu/translations/fa/LC_MESSAGES/messages.po
Normal file
@@ -0,0 +1,731 @@
|
||||
# Persian translations for Mailu.
|
||||
# Copyright (C) 2023 Mailu
|
||||
# This file is distributed under the same license as the Mailu project.
|
||||
# Hossein Hosni <hosni.hossein@gmail.com>, 2023.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: hosni.hossein@gmail.com\n"
|
||||
"POT-Creation-Date: 2023-09-09 00:33+0330\n"
|
||||
"PO-Revision-Date: 2023-09-09 01:21+0330\n"
|
||||
"Last-Translator: Hossein Hosni <hosni.hossein@gmail.com>\n"
|
||||
"Language-Team: Persian <https://translate.tedomum.net/projects/mailu/admin/"
|
||||
"en/>\n"
|
||||
"Language: fa\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==0 || n==1);\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: mailu/sso/forms.py:8 mailu/ui/forms.py:79
|
||||
msgid "E-mail"
|
||||
msgstr "پست الکترونیک"
|
||||
|
||||
#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93
|
||||
#: mailu/ui/forms.py:112 mailu/ui/forms.py:166
|
||||
#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:57
|
||||
msgid "Password"
|
||||
msgstr "گذرواژه"
|
||||
|
||||
#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4
|
||||
#: mailu/ui/templates/sidebar.html:142
|
||||
msgid "Sign in"
|
||||
msgstr "ورود"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8
|
||||
msgid "Admin page for"
|
||||
msgstr "صفحه مدیریت برای"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19
|
||||
msgid "toggle sidebar"
|
||||
msgstr "نوار کناری را تغییر دهید"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37
|
||||
msgid "change language"
|
||||
msgstr "تغییر زبان"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94
|
||||
msgid "Go to"
|
||||
msgstr "برو به"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4
|
||||
#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107
|
||||
msgid "Client setup"
|
||||
msgstr "تنظیمات سرویسگیرنده"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114
|
||||
msgid "Website"
|
||||
msgstr "وب سایت"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120
|
||||
msgid "Help"
|
||||
msgstr "راهنما"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:35
|
||||
#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:127
|
||||
msgid "Register a domain"
|
||||
msgstr "ثبت دامنه"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95
|
||||
#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4
|
||||
#: mailu/ui/templates/user/signup_domain.html:4
|
||||
msgid "Sign up"
|
||||
msgstr "ثبتنام"
|
||||
|
||||
#: mailu/ui/forms.py:33 mailu/ui/forms.py:36
|
||||
msgid "Invalid email address."
|
||||
msgstr "پستالکترونیک اشتباه است."
|
||||
|
||||
#: mailu/ui/forms.py:45
|
||||
msgid "Confirm"
|
||||
msgstr "تایید"
|
||||
|
||||
#: mailu/ui/forms.py:48 mailu/ui/forms.py:58
|
||||
#: mailu/ui/templates/domain/details.html:26
|
||||
#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18
|
||||
msgid "Domain name"
|
||||
msgstr "آدرس دامنه"
|
||||
|
||||
#: mailu/ui/forms.py:49
|
||||
msgid "Maximum user count"
|
||||
msgstr "حداکثر تعداد کاربر"
|
||||
|
||||
#: mailu/ui/forms.py:50
|
||||
msgid "Maximum alias count"
|
||||
msgstr "حداکثر تعداد نام مستعار"
|
||||
|
||||
#: mailu/ui/forms.py:51
|
||||
msgid "Maximum user quota"
|
||||
msgstr "حداکثر سهمیه کاربر"
|
||||
|
||||
#: mailu/ui/forms.py:52
|
||||
msgid "Enable sign-up"
|
||||
msgstr "فعال کردن ثبت نام"
|
||||
|
||||
#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86
|
||||
#: mailu/ui/forms.py:132 mailu/ui/forms.py:144
|
||||
#: mailu/ui/templates/alias/list.html:22 mailu/ui/templates/domain/list.html:22
|
||||
#: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:20
|
||||
#: mailu/ui/templates/user/list.html:24
|
||||
msgid "Comment"
|
||||
msgstr "یادداشت"
|
||||
|
||||
#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75
|
||||
#: mailu/ui/forms.py:88 mailu/ui/forms.py:136 mailu/ui/forms.py:145
|
||||
msgid "Save"
|
||||
msgstr "ذخیره"
|
||||
|
||||
#: mailu/ui/forms.py:59
|
||||
msgid "Initial admin"
|
||||
msgstr "مدیر نخستین"
|
||||
|
||||
#: mailu/ui/forms.py:60
|
||||
msgid "Admin password"
|
||||
msgstr "گذرواژه مدیریت"
|
||||
|
||||
#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94
|
||||
msgid "Confirm password"
|
||||
msgstr "تایید گذرواژه"
|
||||
|
||||
#: mailu/ui/forms.py:63
|
||||
msgid "Create"
|
||||
msgstr "ایجاد"
|
||||
|
||||
#: mailu/ui/forms.py:67
|
||||
msgid "Alternative name"
|
||||
msgstr "نام جایگزین"
|
||||
|
||||
#: mailu/ui/forms.py:72
|
||||
msgid "Relayed domain name"
|
||||
msgstr "نام دامنه میانجی"
|
||||
|
||||
#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19
|
||||
msgid "Remote host"
|
||||
msgstr "میزبان راهدور"
|
||||
|
||||
#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23
|
||||
#: mailu/ui/templates/user/signup_domain.html:16
|
||||
msgid "Quota"
|
||||
msgstr "سهمیه"
|
||||
|
||||
#: mailu/ui/forms.py:83
|
||||
msgid "Allow IMAP access"
|
||||
msgstr "دسترسی IMAP مجاز باشد"
|
||||
|
||||
#: mailu/ui/forms.py:84
|
||||
msgid "Allow POP3 access"
|
||||
msgstr "دسترسی POP3 مجاز باشد"
|
||||
|
||||
#: mailu/ui/forms.py:85 mailu/ui/forms.py:101
|
||||
#: mailu/ui/templates/user/settings.html:15
|
||||
msgid "Displayed name"
|
||||
msgstr "نام نمایشی"
|
||||
|
||||
#: mailu/ui/forms.py:87
|
||||
msgid "Enabled"
|
||||
msgstr "فعال"
|
||||
|
||||
#: mailu/ui/forms.py:92
|
||||
msgid "Email address"
|
||||
msgstr "پست الکترونیکی"
|
||||
|
||||
#: mailu/ui/forms.py:102
|
||||
msgid "Enable spam filter"
|
||||
msgstr "فعالکردن پایش هرزنامه"
|
||||
|
||||
#: mailu/ui/forms.py:103
|
||||
msgid "Enable marking spam mails as read"
|
||||
msgstr "فعالسازی علامتزدن هرزنامه به عنوان خواندهشده"
|
||||
|
||||
#: mailu/ui/forms.py:104
|
||||
msgid "Spam filter tolerance"
|
||||
msgstr "بازه تحمل هرزنامه"
|
||||
|
||||
#: mailu/ui/forms.py:105
|
||||
msgid "Enable forwarding"
|
||||
msgstr "فعالسازی بازارسال"
|
||||
|
||||
#: mailu/ui/forms.py:106
|
||||
msgid "Keep a copy of the emails"
|
||||
msgstr "نگهداری رونوشت از پستالکترونیک"
|
||||
|
||||
#: mailu/ui/forms.py:107 mailu/ui/forms.py:143
|
||||
#: mailu/ui/templates/alias/list.html:21
|
||||
msgid "Destination"
|
||||
msgstr "مقصد"
|
||||
|
||||
#: mailu/ui/forms.py:108
|
||||
msgid "Save settings"
|
||||
msgstr "ذخیره تنظیمات"
|
||||
|
||||
#: mailu/ui/forms.py:113
|
||||
msgid "Password check"
|
||||
msgstr "بررسی گذرواژه"
|
||||
|
||||
#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25
|
||||
msgid "Update password"
|
||||
msgstr "بروزرسانی گذرواژه"
|
||||
|
||||
#: mailu/ui/forms.py:118
|
||||
msgid "Enable automatic reply"
|
||||
msgstr "فعالسازی پاسخگویخودکار"
|
||||
|
||||
#: mailu/ui/forms.py:119
|
||||
msgid "Reply subject"
|
||||
msgstr "موضوع پاسخگویی"
|
||||
|
||||
#: mailu/ui/forms.py:120
|
||||
msgid "Reply body"
|
||||
msgstr "متن پاسخگویی"
|
||||
|
||||
#: mailu/ui/forms.py:122
|
||||
msgid "Start of vacation"
|
||||
msgstr "شروع استراحت"
|
||||
|
||||
#: mailu/ui/forms.py:123
|
||||
msgid "End of vacation"
|
||||
msgstr "پایان استراحت"
|
||||
|
||||
#: mailu/ui/forms.py:124
|
||||
msgid "Update"
|
||||
msgstr "بروزرسانی"
|
||||
|
||||
#: mailu/ui/forms.py:129
|
||||
msgid "Your token (write it down, as it will never be displayed again)"
|
||||
msgstr "کلید شما (کلید را یادداشت کنید، چرا که دوباره نمایش داده نخواهد شد)"
|
||||
|
||||
#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21
|
||||
msgid "Authorized IP"
|
||||
msgstr "Authorized IP"
|
||||
|
||||
#: mailu/ui/forms.py:140
|
||||
msgid "Alias"
|
||||
msgstr "Alias"
|
||||
|
||||
#: mailu/ui/forms.py:142
|
||||
msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)"
|
||||
msgstr "استفاده از قواعد اسکیوال (برای پوشش الایسها)"
|
||||
|
||||
#: mailu/ui/forms.py:149
|
||||
msgid "Admin email"
|
||||
msgstr "افزودن پستالکترونیکی"
|
||||
|
||||
#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168
|
||||
msgid "Submit"
|
||||
msgstr "ثبت"
|
||||
|
||||
#: mailu/ui/forms.py:154
|
||||
msgid "Manager email"
|
||||
msgstr "مدیریت پستالکترونیک"
|
||||
|
||||
#: mailu/ui/forms.py:159
|
||||
msgid "Protocol"
|
||||
msgstr "پرتکل"
|
||||
|
||||
#: mailu/ui/forms.py:162
|
||||
msgid "Hostname or IP"
|
||||
msgstr "نشانی یا آیپی"
|
||||
|
||||
#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20
|
||||
#: mailu/ui/templates/client.html:45
|
||||
msgid "TCP port"
|
||||
msgstr "پورت تیسیپی"
|
||||
|
||||
#: mailu/ui/forms.py:164
|
||||
msgid "Enable TLS"
|
||||
msgstr "فعالسازی TLS"
|
||||
|
||||
#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28
|
||||
#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21
|
||||
msgid "Username"
|
||||
msgstr "نامکاربری"
|
||||
|
||||
#: mailu/ui/forms.py:167
|
||||
msgid "Keep emails on the server"
|
||||
msgstr "نگهداری ایمیل در سرور"
|
||||
|
||||
#: mailu/ui/forms.py:172
|
||||
msgid "Announcement subject"
|
||||
msgstr "موضوع آگهی"
|
||||
|
||||
#: mailu/ui/forms.py:174
|
||||
msgid "Announcement body"
|
||||
msgstr "متن آگهی"
|
||||
|
||||
#: mailu/ui/forms.py:176
|
||||
msgid "Send"
|
||||
msgstr "ارسال"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:4
|
||||
msgid "Public announcement"
|
||||
msgstr "آگهی عمومی"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80
|
||||
#: mailu/ui/templates/user/settings.html:19
|
||||
msgid "Antispam"
|
||||
msgstr "ضدهرزنامه"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:8
|
||||
msgid "RSPAMD status page"
|
||||
msgstr "صفحه گزارش وضعیت RSPAMD"
|
||||
|
||||
#: mailu/ui/templates/client.html:8
|
||||
msgid "configure your email client"
|
||||
msgstr "تنظیمات کاربر پستالکترونیک"
|
||||
|
||||
#: mailu/ui/templates/client.html:13
|
||||
msgid "Incoming mail"
|
||||
msgstr "ایمیل ورودی"
|
||||
|
||||
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41
|
||||
msgid "Mail protocol"
|
||||
msgstr "پرتکل ایمیل"
|
||||
|
||||
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49
|
||||
msgid "Server name"
|
||||
msgstr "نام سرور"
|
||||
|
||||
#: mailu/ui/templates/client.html:38
|
||||
msgid "Outgoing mail"
|
||||
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 "شما در حال انجام %(action)s هستید. لطفا دستور را تایید کنید."
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:4
|
||||
msgid "Docker error"
|
||||
msgstr "خطای داکر"
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:12
|
||||
msgid "An error occurred while talking to the Docker server."
|
||||
msgstr "خطایی در هنگام ارتباط با سرور داکر به وجود آمده است."
|
||||
|
||||
#: mailu/ui/templates/macros.html:129
|
||||
msgid "copy to clipboard"
|
||||
msgstr "کپی در حافظه"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:15
|
||||
msgid "My account"
|
||||
msgstr "حساب من"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37
|
||||
msgid "Settings"
|
||||
msgstr "تنظیمات"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38
|
||||
msgid "Auto-reply"
|
||||
msgstr "پاسخگوی خودکار"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37
|
||||
#: mailu/ui/templates/user/list.html:39
|
||||
msgid "Fetched accounts"
|
||||
msgstr "اکانتها واکشیشده"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4
|
||||
msgid "Authentication tokens"
|
||||
msgstr "کلیدهای احراز هویت"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:56
|
||||
msgid "Administration"
|
||||
msgstr "مدیریت"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:62
|
||||
msgid "Announcement"
|
||||
msgstr "آگهی"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:68
|
||||
msgid "Administrators"
|
||||
msgstr "مدیران"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:74
|
||||
msgid "Relayed domains"
|
||||
msgstr "دامنههای میانجی"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:88
|
||||
msgid "Mail domains"
|
||||
msgstr "دامنههای ایمیل"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:99
|
||||
msgid "Webmail"
|
||||
msgstr "پنلوبایمیل"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:135
|
||||
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:17 mailu/ui/templates/alias/list.html:19
|
||||
#: mailu/ui/templates/alternative/list.html:19
|
||||
#: mailu/ui/templates/domain/list.html:17 mailu/ui/templates/fetch/list.html:19
|
||||
#: mailu/ui/templates/manager/list.html:19
|
||||
#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19
|
||||
#: mailu/ui/templates/user/list.html:19
|
||||
msgid "Actions"
|
||||
msgstr "عملیات"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20
|
||||
#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21
|
||||
msgid "Email"
|
||||
msgstr "پستالکترونیک"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32
|
||||
#: mailu/ui/templates/alternative/list.html:29
|
||||
#: mailu/ui/templates/domain/list.html:34 mailu/ui/templates/fetch/list.html:34
|
||||
#: mailu/ui/templates/manager/list.html:27
|
||||
#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30
|
||||
#: mailu/ui/templates/user/list.html:34
|
||||
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:23
|
||||
#: mailu/ui/templates/alternative/list.html:21
|
||||
#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25
|
||||
#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22
|
||||
#: mailu/ui/templates/user/list.html:25
|
||||
msgid "Created"
|
||||
msgstr "ایجاد شد"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:24
|
||||
#: mailu/ui/templates/alternative/list.html:22
|
||||
#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26
|
||||
#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23
|
||||
#: mailu/ui/templates/user/list.html:26
|
||||
msgid "Last edit"
|
||||
msgstr "آخرین ویرایش"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33
|
||||
#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29
|
||||
#: mailu/ui/templates/user/list.html:33
|
||||
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:20
|
||||
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:30
|
||||
msgid "DNS MX entry"
|
||||
msgstr "DNS MX"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:34
|
||||
msgid "DNS SPF entries"
|
||||
msgstr "DNS SPF"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:40
|
||||
msgid "DKIM public key"
|
||||
msgstr "کلید عمومی DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:44
|
||||
msgid "DNS DKIM entry"
|
||||
msgstr "DNS DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:48
|
||||
msgid "DNS DMARC entry"
|
||||
msgstr "DNS DMARC"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:58
|
||||
msgid "DNS TLSA entry"
|
||||
msgstr "DNS TLSA"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:63
|
||||
msgid "DNS client auto-configuration entries"
|
||||
msgstr "DNS client auto-configuration"
|
||||
|
||||
#: 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:18
|
||||
msgid "Manage"
|
||||
msgstr "مدیریت"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:20
|
||||
msgid "Mailbox count"
|
||||
msgstr "تعداد جعبه ایمیل"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:21
|
||||
msgid "Alias count"
|
||||
msgstr "تعداد الایسها"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:31
|
||||
msgid "Details"
|
||||
msgstr "جزئیات"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:38
|
||||
msgid "Users"
|
||||
msgstr "کاربران"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:39
|
||||
msgid "Aliases"
|
||||
msgstr "الایسها"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:40
|
||||
msgid "Managers"
|
||||
msgstr "مدیران"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:42
|
||||
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 <code>MX</code> points to this server"
|
||||
msgstr ""
|
||||
"برای ثبت دامنه جدید، ابتدا میبایست تنظیمات DNS ٔدامنه را به گونهای تنظیم کنید "
|
||||
"که رکورد <code>MX</code> آن به سرور ما اشاره کند"
|
||||
|
||||
#: mailu/ui/templates/domain/signup.html:18
|
||||
msgid ""
|
||||
"If you do not know how to setup an <code>MX</code> record for your DNS "
|
||||
"zone,\n"
|
||||
" please contact your DNS provider or administrator. Also, please wait a\n"
|
||||
" couple minutes after the <code>MX</code> is set so the local server "
|
||||
"cache\n"
|
||||
" expires."
|
||||
msgstr ""
|
||||
"در صورتی که نمیدانید چگونه تنظیمات رکورد <code>MX</code> دامنه خود را انجام "
|
||||
"دهید، میبایست با فروشنده دامنه خود ارتباط برقرار کنید.\n"
|
||||
"همچنین بعد از تنظیم رکورد <code>MX</code> دقایقی را تا اعمال نتیجه آن منتظر "
|
||||
"بمانید."
|
||||
|
||||
#: 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:20
|
||||
msgid "Endpoint"
|
||||
msgstr "نشانی"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:22
|
||||
msgid "Keep emails"
|
||||
msgstr "نگهداری ایمیلها"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:23
|
||||
msgid "Last check"
|
||||
msgstr "آخرین بررسی"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:24
|
||||
msgid "Status"
|
||||
msgstr "وضعیت"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
msgid "yes"
|
||||
msgstr "بلی"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
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 "افزودن دامنه میانجی"
|
||||
|
||||
#: mailu/ui/templates/relay/edit.html:4
|
||||
msgid "Edit relayed domain"
|
||||
msgstr "ویرایش دامنه میانجی"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:4
|
||||
msgid "Relayed domain list"
|
||||
msgstr "لیست دامنههای میانجی"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:9
|
||||
msgid "New relayed domain"
|
||||
msgstr "دامنه میانجی جدید"
|
||||
|
||||
#: 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:23
|
||||
msgid "Features and quotas"
|
||||
msgstr "قابلیتها و سهمیه"
|
||||
|
||||
#: mailu/ui/templates/user/edit.html:4
|
||||
msgid "Edit user"
|
||||
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:20 mailu/ui/templates/user/settings.html:4
|
||||
msgid "User settings"
|
||||
msgstr "تنظیمات کاربر"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:22
|
||||
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:27
|
||||
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 "دامنه"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:15
|
||||
msgid "Available slots"
|
||||
msgstr "جایگاههای موجود"
|
||||
737
core/admin/mailu/translations/uk/LC_MESSAGES/messages.po
Normal file
737
core/admin/mailu/translations/uk/LC_MESSAGES/messages.po
Normal file
@@ -0,0 +1,737 @@
|
||||
# Ukrainian translations for PROJECT.
|
||||
# Copyright (C) 2016 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# Danylo Sydorenko <sydorenkodanylo2021@gmail.com>, 2023.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: sydorenkodanylo2021@gmail.com\n"
|
||||
"POT-Creation-Date: 2022-05-22 18:47+0200\n"
|
||||
"PO-Revision-Date: 2023-08-14 08:19+0300\n"
|
||||
"Last-Translator: Danylo Sydorenko <sydorenkodanylo2021@gmail.com>\n"
|
||||
"Language-Team: Ukrainian <https://translate.tedomum.net/projects/mailu/admin/"
|
||||
"uk/>\n"
|
||||
"Language: uk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && "
|
||||
"(n%100<10 || n%100>=20) ? 1 : 2\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: mailu/sso/forms.py:8 mailu/ui/forms.py:79
|
||||
msgid "E-mail"
|
||||
msgstr "Електронна пошта"
|
||||
|
||||
#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93
|
||||
#: mailu/ui/forms.py:112 mailu/ui/forms.py:166
|
||||
#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:57
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4
|
||||
#: mailu/ui/templates/sidebar.html:142
|
||||
msgid "Sign in"
|
||||
msgstr "Увійти"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8
|
||||
msgid "Admin page for"
|
||||
msgstr "Сторінка адміністратора для"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19
|
||||
msgid "toggle sidebar"
|
||||
msgstr "переключити бічну панель"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37
|
||||
msgid "change language"
|
||||
msgstr "змінити мову"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94
|
||||
msgid "Go to"
|
||||
msgstr "Перейти до"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4
|
||||
#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107
|
||||
msgid "Client setup"
|
||||
msgstr "Налаштування клієнта"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114
|
||||
msgid "Website"
|
||||
msgstr "Вебсайт"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120
|
||||
msgid "Help"
|
||||
msgstr "Довідка"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:35
|
||||
#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:127
|
||||
msgid "Register a domain"
|
||||
msgstr "Зареєструвати домен"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95
|
||||
#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4
|
||||
#: mailu/ui/templates/user/signup_domain.html:4
|
||||
msgid "Sign up"
|
||||
msgstr "Зареєструватися"
|
||||
|
||||
#: mailu/ui/forms.py:33 mailu/ui/forms.py:36
|
||||
msgid "Invalid email address."
|
||||
msgstr "Недійсна електронна пошта."
|
||||
|
||||
#: mailu/ui/forms.py:45
|
||||
msgid "Confirm"
|
||||
msgstr "Підтвердити"
|
||||
|
||||
#: mailu/ui/forms.py:48 mailu/ui/forms.py:58
|
||||
#: mailu/ui/templates/domain/details.html:26
|
||||
#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18
|
||||
msgid "Domain name"
|
||||
msgstr "Ім'я домену"
|
||||
|
||||
#: mailu/ui/forms.py:49
|
||||
msgid "Maximum user count"
|
||||
msgstr "Максимальна кількість користувачів"
|
||||
|
||||
#: mailu/ui/forms.py:50
|
||||
msgid "Maximum alias count"
|
||||
msgstr "Максимальна кількість скорочень"
|
||||
|
||||
#: mailu/ui/forms.py:51
|
||||
msgid "Maximum user quota"
|
||||
msgstr "Максимальна квота користувачів"
|
||||
|
||||
#: mailu/ui/forms.py:52
|
||||
msgid "Enable sign-up"
|
||||
msgstr "Увімкнути реєстрацію"
|
||||
|
||||
#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86
|
||||
#: mailu/ui/forms.py:132 mailu/ui/forms.py:144
|
||||
#: mailu/ui/templates/alias/list.html:22 mailu/ui/templates/domain/list.html:22
|
||||
#: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:20
|
||||
#: mailu/ui/templates/user/list.html:24
|
||||
msgid "Comment"
|
||||
msgstr "Коментар"
|
||||
|
||||
#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75
|
||||
#: mailu/ui/forms.py:88 mailu/ui/forms.py:136 mailu/ui/forms.py:145
|
||||
msgid "Save"
|
||||
msgstr "Зберегти"
|
||||
|
||||
#: mailu/ui/forms.py:59
|
||||
msgid "Initial admin"
|
||||
msgstr "Початковий адміністратор"
|
||||
|
||||
#: mailu/ui/forms.py:60
|
||||
msgid "Admin password"
|
||||
msgstr "Пароль адміністратора"
|
||||
|
||||
#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94
|
||||
msgid "Confirm password"
|
||||
msgstr "Підтвердити пароль"
|
||||
|
||||
#: mailu/ui/forms.py:63
|
||||
msgid "Create"
|
||||
msgstr "Створити"
|
||||
|
||||
#: mailu/ui/forms.py:67
|
||||
msgid "Alternative name"
|
||||
msgstr "Альтернативна назва"
|
||||
|
||||
#: mailu/ui/forms.py:72
|
||||
msgid "Relayed domain name"
|
||||
msgstr "Ретрансльоване доменне ім'я"
|
||||
|
||||
#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19
|
||||
msgid "Remote host"
|
||||
msgstr "Віддалений хост"
|
||||
|
||||
#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23
|
||||
#: mailu/ui/templates/user/signup_domain.html:16
|
||||
msgid "Quota"
|
||||
msgstr "Квота"
|
||||
|
||||
#: mailu/ui/forms.py:83
|
||||
msgid "Allow IMAP access"
|
||||
msgstr "Дозволити IMAP-доступ"
|
||||
|
||||
#: mailu/ui/forms.py:84
|
||||
msgid "Allow POP3 access"
|
||||
msgstr "Дозволити POP3-доступ"
|
||||
|
||||
#: mailu/ui/forms.py:85 mailu/ui/forms.py:101
|
||||
#: mailu/ui/templates/user/settings.html:15
|
||||
msgid "Displayed name"
|
||||
msgstr "Ім'я, що показується"
|
||||
|
||||
#: mailu/ui/forms.py:87
|
||||
msgid "Enabled"
|
||||
msgstr "Увімкнено"
|
||||
|
||||
#: mailu/ui/forms.py:92
|
||||
msgid "Email address"
|
||||
msgstr "Електронна пошта"
|
||||
|
||||
#: mailu/ui/forms.py:102
|
||||
msgid "Enable spam filter"
|
||||
msgstr "Увімкнути спам-фільтр"
|
||||
|
||||
#: mailu/ui/forms.py:103
|
||||
msgid "Enable marking spam mails as read"
|
||||
msgstr "Позначити спам як прочитане"
|
||||
|
||||
#: mailu/ui/forms.py:104
|
||||
msgid "Spam filter tolerance"
|
||||
msgstr "Толерантність спам-фільтра до спаму"
|
||||
|
||||
#: mailu/ui/forms.py:105
|
||||
msgid "Enable forwarding"
|
||||
msgstr "Увімкнути переадресацію"
|
||||
|
||||
#: mailu/ui/forms.py:106
|
||||
msgid "Keep a copy of the emails"
|
||||
msgstr "Зберігати копії електронних листів"
|
||||
|
||||
#: mailu/ui/forms.py:107 mailu/ui/forms.py:143
|
||||
#: mailu/ui/templates/alias/list.html:21
|
||||
msgid "Destination"
|
||||
msgstr "Призначення"
|
||||
|
||||
#: mailu/ui/forms.py:108
|
||||
msgid "Save settings"
|
||||
msgstr "Зберегти налаштування"
|
||||
|
||||
#: mailu/ui/forms.py:113
|
||||
msgid "Password check"
|
||||
msgstr "Перевірка пароля"
|
||||
|
||||
#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25
|
||||
msgid "Update password"
|
||||
msgstr "Оновити пароль"
|
||||
|
||||
#: mailu/ui/forms.py:118
|
||||
msgid "Enable automatic reply"
|
||||
msgstr "Увімкнути автоматичну відповідь"
|
||||
|
||||
#: mailu/ui/forms.py:119
|
||||
msgid "Reply subject"
|
||||
msgstr "Тема відповіді"
|
||||
|
||||
#: mailu/ui/forms.py:120
|
||||
msgid "Reply body"
|
||||
msgstr "Текст відповіді"
|
||||
|
||||
#: mailu/ui/forms.py:122
|
||||
msgid "Start of vacation"
|
||||
msgstr "Початок відпустки"
|
||||
|
||||
#: mailu/ui/forms.py:123
|
||||
msgid "End of vacation"
|
||||
msgstr "Кінець відпустки"
|
||||
|
||||
#: mailu/ui/forms.py:124
|
||||
msgid "Update"
|
||||
msgstr "Оновити"
|
||||
|
||||
#: mailu/ui/forms.py:129
|
||||
msgid "Your token (write it down, as it will never be displayed again)"
|
||||
msgstr ""
|
||||
"Ваш токен (запишіть його, оскільки він більше ніколи не буде відображатися)"
|
||||
|
||||
#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21
|
||||
msgid "Authorized IP"
|
||||
msgstr "Дозволені IP-адреси"
|
||||
|
||||
#: mailu/ui/forms.py:140
|
||||
msgid "Alias"
|
||||
msgstr "Псевдонім"
|
||||
|
||||
#: mailu/ui/forms.py:142
|
||||
msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)"
|
||||
msgstr ""
|
||||
"Використовуйте синтаксис SQL LIKE (наприклад, для псевдонімів catch-all)"
|
||||
|
||||
#: mailu/ui/forms.py:149
|
||||
msgid "Admin email"
|
||||
msgstr "Пошта адміністратора"
|
||||
|
||||
#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168
|
||||
msgid "Submit"
|
||||
msgstr "Надіслати"
|
||||
|
||||
#: mailu/ui/forms.py:154
|
||||
msgid "Manager email"
|
||||
msgstr "Електронна пошта менеджера"
|
||||
|
||||
#: mailu/ui/forms.py:159
|
||||
msgid "Protocol"
|
||||
msgstr "Протокол"
|
||||
|
||||
#: mailu/ui/forms.py:162
|
||||
msgid "Hostname or IP"
|
||||
msgstr "Ім'я хоста або IP"
|
||||
|
||||
#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20
|
||||
#: mailu/ui/templates/client.html:45
|
||||
msgid "TCP port"
|
||||
msgstr "Порт TCP"
|
||||
|
||||
#: mailu/ui/forms.py:164
|
||||
msgid "Enable TLS"
|
||||
msgstr "Увімкнути TLS"
|
||||
|
||||
#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28
|
||||
#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21
|
||||
msgid "Username"
|
||||
msgstr "Ім'я користувача"
|
||||
|
||||
#: mailu/ui/forms.py:167
|
||||
msgid "Keep emails on the server"
|
||||
msgstr "Зберігати копії електронних листів"
|
||||
|
||||
#: mailu/ui/forms.py:172
|
||||
msgid "Announcement subject"
|
||||
msgstr "Тема оголошення"
|
||||
|
||||
#: mailu/ui/forms.py:174
|
||||
msgid "Announcement body"
|
||||
msgstr "Текст оголошення"
|
||||
|
||||
#: mailu/ui/forms.py:176
|
||||
msgid "Send"
|
||||
msgstr "Надіслати"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:4
|
||||
msgid "Public announcement"
|
||||
msgstr "Публічне оголошення"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80
|
||||
#: mailu/ui/templates/user/settings.html:19
|
||||
msgid "Antispam"
|
||||
msgstr "Антиспам"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:8
|
||||
msgid "RSPAMD status page"
|
||||
msgstr "Сторінка стану RSPAMD"
|
||||
|
||||
#: mailu/ui/templates/client.html:8
|
||||
msgid "configure your email client"
|
||||
msgstr "налаштуйте свій поштовий клієнт"
|
||||
|
||||
#: mailu/ui/templates/client.html:13
|
||||
msgid "Incoming mail"
|
||||
msgstr "Вхідна пошта"
|
||||
|
||||
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41
|
||||
msgid "Mail protocol"
|
||||
msgstr "Поштовий протокол"
|
||||
|
||||
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49
|
||||
msgid "Server name"
|
||||
msgstr "Назва серверу"
|
||||
|
||||
#: mailu/ui/templates/client.html:38
|
||||
msgid "Outgoing mail"
|
||||
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 "Ви збираєтеся виконати %(action)s. Будь ласка, підтвердіть вашу дію."
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:4
|
||||
msgid "Docker error"
|
||||
msgstr "Помилка Docker"
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:12
|
||||
msgid "An error occurred while talking to the Docker server."
|
||||
msgstr "Виникла помилка при спілкуванні з сервером Docker."
|
||||
|
||||
#: mailu/ui/templates/macros.html:129
|
||||
msgid "copy to clipboard"
|
||||
msgstr "скопіювати в буфер обміну"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:15
|
||||
msgid "My account"
|
||||
msgstr "Мій обліковий запис"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37
|
||||
msgid "Settings"
|
||||
msgstr "Налаштування"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38
|
||||
msgid "Auto-reply"
|
||||
msgstr "Автовідповідь"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37
|
||||
#: mailu/ui/templates/user/list.html:39
|
||||
msgid "Fetched accounts"
|
||||
msgstr "Отримані облікові записи"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4
|
||||
msgid "Authentication tokens"
|
||||
msgstr "Токени автентифікації"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:56
|
||||
msgid "Administration"
|
||||
msgstr "Адміністрація"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:62
|
||||
msgid "Announcement"
|
||||
msgstr "Оголошення"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:68
|
||||
msgid "Administrators"
|
||||
msgstr "Адміністратори"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:74
|
||||
msgid "Relayed domains"
|
||||
msgstr "Передані домени"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:88
|
||||
msgid "Mail domains"
|
||||
msgstr "Поштові домени"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:99
|
||||
msgid "Webmail"
|
||||
msgstr "Вебпошта"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:135
|
||||
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:17 mailu/ui/templates/alias/list.html:19
|
||||
#: mailu/ui/templates/alternative/list.html:19
|
||||
#: mailu/ui/templates/domain/list.html:17 mailu/ui/templates/fetch/list.html:19
|
||||
#: mailu/ui/templates/manager/list.html:19
|
||||
#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19
|
||||
#: mailu/ui/templates/user/list.html:19
|
||||
msgid "Actions"
|
||||
msgstr "Дії"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20
|
||||
#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21
|
||||
msgid "Email"
|
||||
msgstr "Електронна пошта"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32
|
||||
#: mailu/ui/templates/alternative/list.html:29
|
||||
#: mailu/ui/templates/domain/list.html:34 mailu/ui/templates/fetch/list.html:34
|
||||
#: mailu/ui/templates/manager/list.html:27
|
||||
#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30
|
||||
#: mailu/ui/templates/user/list.html:34
|
||||
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:23
|
||||
#: mailu/ui/templates/alternative/list.html:21
|
||||
#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25
|
||||
#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22
|
||||
#: mailu/ui/templates/user/list.html:25
|
||||
msgid "Created"
|
||||
msgstr "Створено"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:24
|
||||
#: mailu/ui/templates/alternative/list.html:22
|
||||
#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26
|
||||
#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23
|
||||
#: mailu/ui/templates/user/list.html:26
|
||||
msgid "Last edit"
|
||||
msgstr "Останнє редагування від"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33
|
||||
#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29
|
||||
#: mailu/ui/templates/user/list.html:33
|
||||
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:20
|
||||
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:30
|
||||
msgid "DNS MX entry"
|
||||
msgstr "Запис DNS MX"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:34
|
||||
msgid "DNS SPF entries"
|
||||
msgstr "Записи DNS SPF"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:40
|
||||
msgid "DKIM public key"
|
||||
msgstr "Відкритий ключ DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:44
|
||||
msgid "DNS DKIM entry"
|
||||
msgstr "Запис DNS DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:48
|
||||
msgid "DNS DMARC entry"
|
||||
msgstr "Запис DNS DMARC"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:58
|
||||
msgid "DNS TLSA entry"
|
||||
msgstr "Запис DNS TLSA"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:63
|
||||
msgid "DNS client auto-configuration entries"
|
||||
msgstr "Записи автоконфігурації DNS-клієнта"
|
||||
|
||||
#: 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:18
|
||||
msgid "Manage"
|
||||
msgstr "Керувати"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:20
|
||||
msgid "Mailbox count"
|
||||
msgstr "Кількість поштових скриньок"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:21
|
||||
msgid "Alias count"
|
||||
msgstr "Кількість псевдонімів"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:31
|
||||
msgid "Details"
|
||||
msgstr "Подробиці"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:38
|
||||
msgid "Users"
|
||||
msgstr "Користувачі"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:39
|
||||
msgid "Aliases"
|
||||
msgstr "Скорочення"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:40
|
||||
msgid "Managers"
|
||||
msgstr "Менеджери"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:42
|
||||
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 <code>MX</code> points to this server"
|
||||
msgstr ""
|
||||
"Для того, щоб зареєструвати новий домен, необхідно\n"
|
||||
" спочатку налаштувати доменну <code>MX</code> так,\n"
|
||||
" щоб домен вказував на цей сервер"
|
||||
|
||||
#: mailu/ui/templates/domain/signup.html:18
|
||||
msgid ""
|
||||
"If you do not know how to setup an <code>MX</code> record for your DNS "
|
||||
"zone,\n"
|
||||
" please contact your DNS provider or administrator. Also, please wait a\n"
|
||||
" couple minutes after the <code>MX</code> is set so the local server "
|
||||
"cache\n"
|
||||
" expires."
|
||||
msgstr ""
|
||||
"Якщо ви не знаєте, як налаштувати <code>MX</code>-запис для вашої DNS-зони,\n"
|
||||
" зверніться до вашого DNS-провайдера або адміністратора. Також, будь "
|
||||
"ласка, \n"
|
||||
" зачекайте кілька хвилин після встановлення MX, щоб закінчився термін дії "
|
||||
"кешу\n"
|
||||
" локального сервера."
|
||||
|
||||
#: 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:20
|
||||
msgid "Endpoint"
|
||||
msgstr "Кінцева точка"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:22
|
||||
msgid "Keep emails"
|
||||
msgstr "Зберігати електронні листи"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:23
|
||||
msgid "Last check"
|
||||
msgstr "Остання перевірка"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:24
|
||||
msgid "Status"
|
||||
msgstr "Статус"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
msgid "yes"
|
||||
msgstr "так"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
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 "Новий домен ретрансляції"
|
||||
|
||||
#: mailu/ui/templates/relay/edit.html:4
|
||||
msgid "Edit relayed domain"
|
||||
msgstr "Редагувати домен ретрансляції"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:4
|
||||
msgid "Relayed domain list"
|
||||
msgstr "Список ретрансляційний доменів"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:9
|
||||
msgid "New relayed domain"
|
||||
msgstr "Новий ретрансляційний домен"
|
||||
|
||||
#: 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:23
|
||||
msgid "Features and quotas"
|
||||
msgstr "Можливості та квоти"
|
||||
|
||||
#: mailu/ui/templates/user/edit.html:4
|
||||
msgid "Edit user"
|
||||
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:20 mailu/ui/templates/user/settings.html:4
|
||||
msgid "User settings"
|
||||
msgstr "Налаштування користувача"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:22
|
||||
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:27
|
||||
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 "Домен"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:15
|
||||
msgid "Available slots"
|
||||
msgstr "Наявні слоти"
|
||||
716
core/admin/mailu/translations/zh_TW/LC_MESSAGES/messages.po
Normal file
716
core/admin/mailu/translations/zh_TW/LC_MESSAGES/messages.po
Normal file
@@ -0,0 +1,716 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Mailu\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2023-09-13 15:04+0800\n"
|
||||
"PO-Revision-Date: 2023-09-19 13:48+0800\n"
|
||||
"Last-Translator: Jonathan Tsai <tryweb@ichiayi.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: zh_TW\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
|
||||
#: mailu/sso/forms.py:8 mailu/ui/forms.py:79
|
||||
msgid "E-mail"
|
||||
msgstr "電子郵件"
|
||||
|
||||
#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93 mailu/ui/forms.py:112
|
||||
#: mailu/ui/forms.py:166 mailu/ui/templates/client.html:32
|
||||
#: mailu/ui/templates/client.html:57
|
||||
msgid "Password"
|
||||
msgstr "密碼"
|
||||
|
||||
#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4
|
||||
#: mailu/ui/templates/sidebar.html:142
|
||||
msgid "Sign in"
|
||||
msgstr "登入"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8
|
||||
msgid "Admin page for"
|
||||
msgstr "系統管理頁面 -"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19
|
||||
msgid "toggle sidebar"
|
||||
msgstr "切換側邊欄"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37
|
||||
msgid "change language"
|
||||
msgstr "切換語言"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94
|
||||
msgid "Go to"
|
||||
msgstr "跳至"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4
|
||||
#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107
|
||||
msgid "Client setup"
|
||||
msgstr "用戶端設定"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114
|
||||
msgid "Website"
|
||||
msgstr "網站"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120
|
||||
msgid "Help"
|
||||
msgstr "輔助"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:35 mailu/ui/templates/domain/signup.html:4
|
||||
#: mailu/ui/templates/sidebar.html:127
|
||||
msgid "Register a domain"
|
||||
msgstr "註冊網域"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95
|
||||
#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4
|
||||
#: mailu/ui/templates/user/signup_domain.html:4
|
||||
msgid "Sign up"
|
||||
msgstr "註冊"
|
||||
|
||||
#: mailu/ui/forms.py:33 mailu/ui/forms.py:36
|
||||
msgid "Invalid email address."
|
||||
msgstr "電子郵件格式錯誤"
|
||||
|
||||
#: mailu/ui/forms.py:45
|
||||
msgid "Confirm"
|
||||
msgstr "確定"
|
||||
|
||||
#: mailu/ui/forms.py:48 mailu/ui/forms.py:58 mailu/ui/templates/domain/details.html:26
|
||||
#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18
|
||||
msgid "Domain name"
|
||||
msgstr "網域"
|
||||
|
||||
#: mailu/ui/forms.py:49
|
||||
msgid "Maximum user count"
|
||||
msgstr "最大用戶數"
|
||||
|
||||
#: mailu/ui/forms.py:50
|
||||
msgid "Maximum alias count"
|
||||
msgstr "最大別名數"
|
||||
|
||||
#: mailu/ui/forms.py:51
|
||||
msgid "Maximum user quota"
|
||||
msgstr "最大用戶配額"
|
||||
|
||||
#: mailu/ui/forms.py:52
|
||||
msgid "Enable sign-up"
|
||||
msgstr "啟用註冊"
|
||||
|
||||
#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86 mailu/ui/forms.py:132
|
||||
#: mailu/ui/forms.py:144 mailu/ui/templates/alias/list.html:22
|
||||
#: mailu/ui/templates/domain/list.html:22 mailu/ui/templates/relay/list.html:20
|
||||
#: mailu/ui/templates/token/list.html:20 mailu/ui/templates/user/list.html:24
|
||||
msgid "Comment"
|
||||
msgstr "說明"
|
||||
|
||||
#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75 mailu/ui/forms.py:88
|
||||
#: mailu/ui/forms.py:136 mailu/ui/forms.py:145
|
||||
msgid "Save"
|
||||
msgstr "儲存"
|
||||
|
||||
#: mailu/ui/forms.py:59
|
||||
msgid "Initial admin"
|
||||
msgstr "初始管理員"
|
||||
|
||||
#: mailu/ui/forms.py:60
|
||||
msgid "Admin password"
|
||||
msgstr "管理員密碼"
|
||||
|
||||
#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94
|
||||
msgid "Confirm password"
|
||||
msgstr "確認密碼"
|
||||
|
||||
#: mailu/ui/forms.py:63
|
||||
msgid "Create"
|
||||
msgstr "建立"
|
||||
|
||||
#: mailu/ui/forms.py:67
|
||||
msgid "Alternative name"
|
||||
msgstr "替代名稱"
|
||||
|
||||
#: mailu/ui/forms.py:72
|
||||
msgid "Relayed domain name"
|
||||
msgstr "轉發網網域稱"
|
||||
|
||||
#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19
|
||||
msgid "Remote host"
|
||||
msgstr "遠端主機"
|
||||
|
||||
#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23
|
||||
#: mailu/ui/templates/user/signup_domain.html:16
|
||||
msgid "Quota"
|
||||
msgstr "配額"
|
||||
|
||||
#: mailu/ui/forms.py:83
|
||||
msgid "Allow IMAP access"
|
||||
msgstr "允許IMAP存取"
|
||||
|
||||
#: mailu/ui/forms.py:84
|
||||
msgid "Allow POP3 access"
|
||||
msgstr "允許POP3存取"
|
||||
|
||||
#: mailu/ui/forms.py:85 mailu/ui/forms.py:101 mailu/ui/templates/user/settings.html:15
|
||||
msgid "Displayed name"
|
||||
msgstr "顯示名稱"
|
||||
|
||||
#: mailu/ui/forms.py:87
|
||||
msgid "Enabled"
|
||||
msgstr "啟用"
|
||||
|
||||
#: mailu/ui/forms.py:92
|
||||
msgid "Email address"
|
||||
msgstr "電郵地址"
|
||||
|
||||
#: mailu/ui/forms.py:102
|
||||
msgid "Enable spam filter"
|
||||
msgstr "啟用垃圾郵件過濾"
|
||||
|
||||
#: mailu/ui/forms.py:103
|
||||
msgid "Enable marking spam mails as read"
|
||||
msgstr "啟用標註垃圾信已讀"
|
||||
|
||||
#: mailu/ui/forms.py:104
|
||||
msgid "Spam filter tolerance"
|
||||
msgstr "垃圾郵件過濾器容忍度"
|
||||
|
||||
#: mailu/ui/forms.py:105
|
||||
msgid "Enable forwarding"
|
||||
msgstr "啟用轉寄"
|
||||
|
||||
#: mailu/ui/forms.py:106
|
||||
msgid "Keep a copy of the emails"
|
||||
msgstr "保留電子郵件副本"
|
||||
|
||||
#: mailu/ui/forms.py:107 mailu/ui/forms.py:143 mailu/ui/templates/alias/list.html:21
|
||||
msgid "Destination"
|
||||
msgstr "目標地址"
|
||||
|
||||
#: mailu/ui/forms.py:108
|
||||
msgid "Save settings"
|
||||
msgstr "儲存設定"
|
||||
|
||||
#: mailu/ui/forms.py:113
|
||||
msgid "Password check"
|
||||
msgstr "檢查密碼"
|
||||
|
||||
#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25
|
||||
msgid "Update password"
|
||||
msgstr "修改密碼"
|
||||
|
||||
#: mailu/ui/forms.py:118
|
||||
msgid "Enable automatic reply"
|
||||
msgstr "啟用自動回信"
|
||||
|
||||
#: mailu/ui/forms.py:119
|
||||
msgid "Reply subject"
|
||||
msgstr "回信主旨"
|
||||
|
||||
#: mailu/ui/forms.py:120
|
||||
msgid "Reply body"
|
||||
msgstr "回信內文"
|
||||
|
||||
#: mailu/ui/forms.py:122
|
||||
msgid "Start of vacation"
|
||||
msgstr "休假開始"
|
||||
|
||||
#: mailu/ui/forms.py:123
|
||||
msgid "End of vacation"
|
||||
msgstr "休假結束"
|
||||
|
||||
#: mailu/ui/forms.py:124
|
||||
msgid "Update"
|
||||
msgstr "修改"
|
||||
|
||||
#: mailu/ui/forms.py:129
|
||||
msgid "Your token (write it down, as it will never be displayed again)"
|
||||
msgstr "您的 Token(請記下,因為之後不再顯示)"
|
||||
|
||||
#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21
|
||||
msgid "Authorized IP"
|
||||
msgstr "授權IP"
|
||||
|
||||
#: mailu/ui/forms.py:140
|
||||
msgid "Alias"
|
||||
msgstr "別名"
|
||||
|
||||
#: mailu/ui/forms.py:142
|
||||
msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)"
|
||||
msgstr "使用SQL LIKE語法(例如,用於萬用字符別名)"
|
||||
|
||||
#: mailu/ui/forms.py:149
|
||||
msgid "Admin email"
|
||||
msgstr "系統管理員信箱"
|
||||
|
||||
#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168
|
||||
msgid "Submit"
|
||||
msgstr "送出"
|
||||
|
||||
#: mailu/ui/forms.py:154
|
||||
msgid "Manager email"
|
||||
msgstr "管理員信箱"
|
||||
|
||||
#: mailu/ui/forms.py:159
|
||||
msgid "Protocol"
|
||||
msgstr "協定"
|
||||
|
||||
#: mailu/ui/forms.py:162
|
||||
msgid "Hostname or IP"
|
||||
msgstr "主機名稱或IP"
|
||||
|
||||
#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20
|
||||
#: mailu/ui/templates/client.html:45
|
||||
msgid "TCP port"
|
||||
msgstr "TCP埠口"
|
||||
|
||||
#: mailu/ui/forms.py:164
|
||||
msgid "Enable TLS"
|
||||
msgstr "啟用TLS"
|
||||
|
||||
#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28
|
||||
#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21
|
||||
msgid "Username"
|
||||
msgstr "用户名"
|
||||
|
||||
#: mailu/ui/forms.py:167
|
||||
msgid "Keep emails on the server"
|
||||
msgstr "在主機上保留電子郵件"
|
||||
|
||||
#: mailu/ui/forms.py:172
|
||||
msgid "Announcement subject"
|
||||
msgstr "公告主旨"
|
||||
|
||||
#: mailu/ui/forms.py:174
|
||||
msgid "Announcement body"
|
||||
msgstr "公告內文"
|
||||
|
||||
#: mailu/ui/forms.py:176
|
||||
msgid "Send"
|
||||
msgstr "寄出"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:4
|
||||
msgid "Public announcement"
|
||||
msgstr "公告通知"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80
|
||||
#: mailu/ui/templates/user/settings.html:19
|
||||
msgid "Antispam"
|
||||
msgstr "反垃圾郵件"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:8
|
||||
msgid "RSPAMD status page"
|
||||
msgstr "RSPAMD 狀態頁面"
|
||||
|
||||
#: mailu/ui/templates/client.html:8
|
||||
msgid "configure your email client"
|
||||
msgstr "設定電子郵件用戶端"
|
||||
|
||||
#: mailu/ui/templates/client.html:13
|
||||
msgid "Incoming mail"
|
||||
msgstr "接收郵件"
|
||||
|
||||
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41
|
||||
msgid "Mail protocol"
|
||||
msgstr "郵件協議"
|
||||
|
||||
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49
|
||||
msgid "Server name"
|
||||
msgstr "主機名稱"
|
||||
|
||||
#: mailu/ui/templates/client.html:38
|
||||
msgid "Outgoing mail"
|
||||
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 "即將執行%(action)s,請確認您的動作。"
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:4
|
||||
msgid "Docker error"
|
||||
msgstr "Docker錯誤"
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:12
|
||||
msgid "An error occurred while talking to the Docker server."
|
||||
msgstr "Docker主機通信出錯"
|
||||
|
||||
#: mailu/ui/templates/macros.html:129
|
||||
msgid "copy to clipboard"
|
||||
msgstr "複製到剪貼簿"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:15
|
||||
msgid "My account"
|
||||
msgstr "我的帳號"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37
|
||||
msgid "Settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38
|
||||
msgid "Auto-reply"
|
||||
msgstr "自動回信"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37
|
||||
#: mailu/ui/templates/user/list.html:39
|
||||
msgid "Fetched accounts"
|
||||
msgstr "代收帳號"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4
|
||||
msgid "Authentication tokens"
|
||||
msgstr "認證Token"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:56
|
||||
msgid "Administration"
|
||||
msgstr "管理"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:62
|
||||
msgid "Announcement"
|
||||
msgstr "公告"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:68
|
||||
msgid "Administrators"
|
||||
msgstr "系統管理員"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:74
|
||||
msgid "Relayed domains"
|
||||
msgstr "轉發網域"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:88
|
||||
msgid "Mail domains"
|
||||
msgstr "郵件網域"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:99
|
||||
msgid "Webmail"
|
||||
msgstr "網頁信箱"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:135
|
||||
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:17 mailu/ui/templates/alias/list.html:19
|
||||
#: mailu/ui/templates/alternative/list.html:19 mailu/ui/templates/domain/list.html:17
|
||||
#: mailu/ui/templates/fetch/list.html:19 mailu/ui/templates/manager/list.html:19
|
||||
#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19
|
||||
#: mailu/ui/templates/user/list.html:19
|
||||
msgid "Actions"
|
||||
msgstr "操作"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20
|
||||
#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21
|
||||
msgid "Email"
|
||||
msgstr "電子郵件"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32
|
||||
#: mailu/ui/templates/alternative/list.html:29 mailu/ui/templates/domain/list.html:34
|
||||
#: mailu/ui/templates/fetch/list.html:34 mailu/ui/templates/manager/list.html:27
|
||||
#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30
|
||||
#: mailu/ui/templates/user/list.html:34
|
||||
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:23 mailu/ui/templates/alternative/list.html:21
|
||||
#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25
|
||||
#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22
|
||||
#: mailu/ui/templates/user/list.html:25
|
||||
msgid "Created"
|
||||
msgstr "已建立"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:24 mailu/ui/templates/alternative/list.html:22
|
||||
#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26
|
||||
#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23
|
||||
#: mailu/ui/templates/user/list.html:26
|
||||
msgid "Last edit"
|
||||
msgstr "上次修改"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33
|
||||
#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29
|
||||
#: mailu/ui/templates/user/list.html:33
|
||||
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:20
|
||||
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:30
|
||||
msgid "DNS MX entry"
|
||||
msgstr "DNS MX條目"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:34
|
||||
msgid "DNS SPF entries"
|
||||
msgstr "DNS SPF條目"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:40
|
||||
msgid "DKIM public key"
|
||||
msgstr "DKIM公鑰"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:44
|
||||
msgid "DNS DKIM entry"
|
||||
msgstr "DNS DKIM條目"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:48
|
||||
msgid "DNS DMARC entry"
|
||||
msgstr "DNS DMARC條目"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:58
|
||||
msgid "DNS TLSA entry"
|
||||
msgstr "DNS TLSA條目"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:63
|
||||
msgid "DNS client auto-configuration entries"
|
||||
msgstr "DNS 客戶端自動設定條目"
|
||||
|
||||
#: 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:18
|
||||
msgid "Manage"
|
||||
msgstr "管理"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:20
|
||||
msgid "Mailbox count"
|
||||
msgstr "信箱数量"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:21
|
||||
msgid "Alias count"
|
||||
msgstr "別名数量"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:31
|
||||
msgid "Details"
|
||||
msgstr "詳細資訊"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:38
|
||||
msgid "Users"
|
||||
msgstr "用户"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:39
|
||||
msgid "Aliases"
|
||||
msgstr "別名"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:40
|
||||
msgid "Managers"
|
||||
msgstr "管理員"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:42
|
||||
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 <code>MX</code> points to this server"
|
||||
msgstr "為了註冊新網域,您必須先設定網域,並將網域 <code>MX</code> 指向這伺服主機。"
|
||||
|
||||
#: mailu/ui/templates/domain/signup.html:18
|
||||
msgid ""
|
||||
"If you do not know how to setup an <code>MX</code> record for your DNS zone,\n"
|
||||
" please contact your DNS provider or administrator. Also, please wait a\n"
|
||||
" couple minutes after the <code>MX</code> is set so the local server cache\n"
|
||||
" expires."
|
||||
msgstr ""
|
||||
"如果您不知道如何為您的 DNS 網域設定 <code>MX</code> 記錄,請聯繫您的 DNS 提供商或管理員。"
|
||||
"此外,請在設定 <code>MX</code> 之後等待幾分鐘 讓本地伺服主機的快取過期。"
|
||||
|
||||
#: 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:20
|
||||
msgid "Endpoint"
|
||||
msgstr "端點"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:22
|
||||
msgid "Keep emails"
|
||||
msgstr "保留電子郵件"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:23
|
||||
msgid "Last check"
|
||||
msgstr "上次檢查"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:24
|
||||
msgid "Status"
|
||||
msgstr "狀態"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
msgid "yes"
|
||||
msgstr "是"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
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 "新的轉發網域"
|
||||
|
||||
#: mailu/ui/templates/relay/edit.html:4
|
||||
msgid "Edit relayed domain"
|
||||
msgstr "修改轉發網域"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:4
|
||||
msgid "Relayed domain list"
|
||||
msgstr "轉發網域列表"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:9
|
||||
msgid "New relayed domain"
|
||||
msgstr "新的轉發網域"
|
||||
|
||||
#: mailu/ui/templates/token/create.html:4
|
||||
msgid "Create an authentication token"
|
||||
msgstr "建立一個認證Token"
|
||||
|
||||
#: mailu/ui/templates/token/list.html:12
|
||||
msgid "New token"
|
||||
msgstr "新Token"
|
||||
|
||||
#: 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:23
|
||||
msgid "Features and quotas"
|
||||
msgstr "功能和配額"
|
||||
|
||||
#: mailu/ui/templates/user/edit.html:4
|
||||
msgid "Edit user"
|
||||
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:20 mailu/ui/templates/user/settings.html:4
|
||||
msgid "User settings"
|
||||
msgstr "用户設定"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:22
|
||||
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:27
|
||||
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 "網域"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:15
|
||||
msgid "Available slots"
|
||||
msgstr "剩餘名額"
|
||||
|
||||
#~ msgid "to access the administration tools"
|
||||
#~ msgstr "存取管理工具"
|
||||
|
||||
#~ msgid "Forward emails"
|
||||
#~ msgstr "轉寄郵件"
|
||||
@@ -60,6 +60,6 @@
|
||||
{%- endcall %}
|
||||
<blockquote>
|
||||
{% trans %}If you use an Apple device,{% endtrans %}
|
||||
<a href="/apple.mobileconfig">{% trans %}click here to autoconfigure it.{% endtrans %}</a>
|
||||
<a href="/apple.mobileconfig">{% trans %}click here to auto-configure it.{% endtrans %}</a>
|
||||
</blockquote>
|
||||
{%- endblock %}
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
|
||||
{%- block main_action %}
|
||||
{%- if current_user.global_admin %}
|
||||
<a class="btn btn-primary float-right" href="{{ url_for(".domain_genkeys", domain_name=domain.name) }}">
|
||||
<a class="btn btn-primary btn-group float-right" href="{{ url_for(".domain_genkeys", domain_name=domain.name) }}">
|
||||
{%- if domain.dkim_publickey %}
|
||||
{% trans %}Regenerate keys{% endtrans %}
|
||||
{%- else %}
|
||||
{% trans %}Generate keys{% endtrans %}
|
||||
{%- endif %}
|
||||
</a>
|
||||
</a><a style="margin-right: 5px;" class="btn btn-primary btn-group float-right" href="{{ url_for(".domain_download_zonefile", domain_name=domain.name) }}"> {% trans %}Download zonefile{% endtrans %}</a>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -36,10 +36,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
{%- if domain.dkim_publickey %}
|
||||
<tr>
|
||||
<th>{% trans %}DKIM public key{% endtrans %}</th>
|
||||
<td>{{ macros.clip("dkim_key") }}<pre id="dkim_key" class="pre-config border bg-light">{{ domain.dkim_publickey }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans %}DNS DKIM entry{% endtrans %}</th>
|
||||
<td>{{ macros.clip("dns_dkim") }}<pre id="dns_dkim" class="pre-config border bg-light">{{ domain.dns_dkim }}</pre></td>
|
||||
|
||||
@@ -70,6 +70,27 @@ def domain_details(domain_name):
|
||||
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||
return flask.render_template('domain/details.html', domain=domain)
|
||||
|
||||
@ui.route('/domain/details/<domain_name>/zonefile', methods=['GET'])
|
||||
@access.domain_admin(models.Domain, 'domain_name')
|
||||
def domain_download_zonefile(domain_name):
|
||||
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||
res = [domain.dns_mx, domain.dns_spf]
|
||||
if domain.dkim_publickey:
|
||||
record = domain.dns_dkim.split('"', 1)[0].strip()
|
||||
txt = f'v=DKIM1; k=rsa; p={domain.dkim_publickey}'
|
||||
txt = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250))
|
||||
res.append(f'{record} {txt}')
|
||||
res.append(domain.dns_dmarc)
|
||||
if domain.dns_tlsa:
|
||||
res.append(domain.dns_tlsa)
|
||||
res.extend(domain.dns_autoconfig)
|
||||
res.append("")
|
||||
return flask.Response(
|
||||
"\n".join(res),
|
||||
content_type="text/plain",
|
||||
headers={"Content-disposition": f"attachment; filename={domain.name}-zonefile.txt"}
|
||||
)
|
||||
|
||||
|
||||
@ui.route('/domain/genkeys/<domain_name>', methods=['GET', 'POST'])
|
||||
@access.domain_admin(models.Domain, 'domain_name')
|
||||
|
||||
@@ -100,7 +100,6 @@ def is_ip_in_subnet(ip, subnets=[]):
|
||||
# Application translation
|
||||
babel = flask_babel.Babel()
|
||||
|
||||
@babel.localeselector
|
||||
def get_locale():
|
||||
""" selects locale for translation """
|
||||
if not app.config['SESSION_COOKIE_NAME'] in flask.request.cookies:
|
||||
@@ -310,7 +309,7 @@ class MailuSessionConfig:
|
||||
# default size of session key parts
|
||||
uid_bits = 64 # default if SESSION_KEY_BITS is not set in config
|
||||
sid_bits = 128 # for now. must be multiple of 8!
|
||||
time_bits = 32 # for now. must be multiple of 8!
|
||||
time_bits = 32 # for now. must be multiple of 8!
|
||||
|
||||
def __init__(self, app=None):
|
||||
|
||||
@@ -400,7 +399,7 @@ class MailuSessionInterface(SessionInterface):
|
||||
if session.modified:
|
||||
session.delete()
|
||||
response.delete_cookie(
|
||||
app.session_cookie_name,
|
||||
app.config['SESSION_COOKIE_NAME'],
|
||||
domain=self.get_cookie_domain(app),
|
||||
path=self.get_cookie_path(app),
|
||||
)
|
||||
@@ -413,7 +412,7 @@ class MailuSessionInterface(SessionInterface):
|
||||
# save session and update cookie if necessary
|
||||
if session.save():
|
||||
response.set_cookie(
|
||||
app.session_cookie_name,
|
||||
app.config['SESSION_COOKIE_NAME'],
|
||||
session.sid,
|
||||
expires=datetime.now()+timedelta(seconds=app.config['PERMANENT_SESSION_LIFETIME']),
|
||||
httponly=self.get_cookie_httponly(app),
|
||||
@@ -473,29 +472,30 @@ class MailuSessionExtension:
|
||||
def init_app(self, app):
|
||||
""" Replace session management of application. """
|
||||
|
||||
redis_session = False
|
||||
|
||||
if app.config.get('MEMORY_SESSIONS'):
|
||||
# in-memory session store for use in development
|
||||
app.session_store = DictStore()
|
||||
|
||||
else:
|
||||
# redis-based session store for use in production
|
||||
redis_session = True
|
||||
app.session_store = RedisStore(
|
||||
redis.StrictRedis().from_url(app.config['SESSION_STORAGE_URL'])
|
||||
)
|
||||
|
||||
app.session_config = MailuSessionConfig(app)
|
||||
app.session_interface = MailuSessionInterface()
|
||||
if redis_session:
|
||||
# clean expired sessions once on first use in case lifetime was changed
|
||||
def cleaner():
|
||||
with app.app_context():
|
||||
with cleaned.get_lock():
|
||||
if not cleaned.value:
|
||||
cleaned.value = True
|
||||
app.logger.info('cleaning session store')
|
||||
MailuSessionExtension.cleanup_sessions(app)
|
||||
|
||||
app.before_first_request(cleaner)
|
||||
|
||||
app.session_config = MailuSessionConfig(app)
|
||||
app.session_interface = MailuSessionInterface()
|
||||
|
||||
cleaned = Value('i', False)
|
||||
session = MailuSessionExtension()
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ ENV \
|
||||
\
|
||||
ADMIN_ADDRESS="127.0.0.1" \
|
||||
FRONT_ADDRESS="127.0.0.1" \
|
||||
FTS_ATTACHMENTS_ADDRESS="127.0.0.1" \
|
||||
SMTP_ADDRESS="127.0.0.1" \
|
||||
IMAP_ADDRESS="127.0.0.1" \
|
||||
REDIS_ADDRESS="127.0.0.1" \
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import time
|
||||
import logging as log
|
||||
import sys
|
||||
from socrate import system
|
||||
@@ -23,6 +25,14 @@ if account is not None and domain is not None and password is not None:
|
||||
log.info("Creating initial admin account %s@%s with mode %s", account, domain, mode)
|
||||
os.system("flask mailu admin %s %s '%s' --mode %s" % (account, domain, password, mode))
|
||||
|
||||
def test_unsupported():
|
||||
import codecs
|
||||
if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None) or os.environ.get(codecs.decode('ZNVYH_URYZ_PUNEG', 'rot13'), None):
|
||||
return
|
||||
log.critical('Your system is not supported. Please start by reading the documentation and then http://www.catb.org/~esr/faqs/smart-questions.html')
|
||||
while True:
|
||||
time.sleep(5)
|
||||
|
||||
def test_DNS():
|
||||
import dns.resolver
|
||||
import dns.exception
|
||||
@@ -50,6 +60,7 @@ def test_DNS():
|
||||
time.sleep(5)
|
||||
|
||||
test_DNS()
|
||||
test_unsupported()
|
||||
|
||||
cmdline = [
|
||||
"gunicorn",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# base system image (intermediate)
|
||||
# Note when updating the alpine tag, first manually run the workflow .github/workflows/mirror.yml.
|
||||
# Just run the workflow with the tag that must be synchronised.
|
||||
ARG DISTRO=ghcr.io/mailu/alpine:3.17.2
|
||||
ARG DISTRO=ghcr.io/mailu/alpine:3.18.4
|
||||
FROM $DISTRO as system
|
||||
|
||||
ENV TZ=Etc/UTC LANG=C.UTF-8
|
||||
@@ -16,7 +16,7 @@ RUN set -euxo pipefail \
|
||||
; 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 \
|
||||
; ! [[ "$(uname -m)" == x86_64 ]] \
|
||||
|| apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc==11-r0
|
||||
|| apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -27,7 +27,7 @@ CMD /bin/bash
|
||||
FROM system as build
|
||||
|
||||
ARG MAILU_DEPS=prod
|
||||
ARG SNUFFLEUPAGUS_VERSION=0.9.0
|
||||
ARG SNUFFLEUPAGUS_VERSION=0.10.0
|
||||
|
||||
ENV VIRTUAL_ENV=/app/venv
|
||||
|
||||
@@ -79,9 +79,9 @@ COPY --chown=root:root --from=build /app/snuffleupagus.so /usr/lib/php81/modules
|
||||
ENV \
|
||||
VIRTUAL_ENV=/app/venv \
|
||||
PATH="/app/venv/bin:${PATH}" \
|
||||
LD_PRELOAD="/usr/lib/libhardened_malloc.so" \
|
||||
ADMIN_ADDRESS="admin" \
|
||||
FRONT_ADDRESS="front" \
|
||||
FTS_ATTACHMENTS_ADDRESS="tika" \
|
||||
SMTP_ADDRESS="smtp" \
|
||||
IMAP_ADDRESS="imap" \
|
||||
OLETOOLS_ADDRESS="oletools" \
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import hmac
|
||||
import logging as log
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import re
|
||||
from pwd import getpwnam
|
||||
import socket
|
||||
import tenacity
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
@tenacity.retry(stop=tenacity.stop_after_attempt(100),
|
||||
wait=tenacity.wait_random(min=2, max=5))
|
||||
@@ -27,7 +30,7 @@ def _coerce_value(value):
|
||||
return value
|
||||
|
||||
class LogFilter(object):
|
||||
def __init__(self, stream, re_patterns, log_file):
|
||||
def __init__(self, stream, re_patterns):
|
||||
self.stream = stream
|
||||
if isinstance(re_patterns, list):
|
||||
self.pattern = re.compile('|'.join([f'(?:{pattern})' for pattern in re_patterns]))
|
||||
@@ -36,7 +39,6 @@ class LogFilter(object):
|
||||
else:
|
||||
self.pattern = re_patterns
|
||||
self.found = False
|
||||
self.log_file = log_file
|
||||
|
||||
def __getattr__(self, attr_name):
|
||||
return getattr(self.stream, attr_name)
|
||||
@@ -48,12 +50,6 @@ class LogFilter(object):
|
||||
if not self.pattern.search(data):
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
if self.log_file:
|
||||
try:
|
||||
with open(self.log_file, 'a', encoding='utf-8') as l:
|
||||
l.write(data)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# caught bad pattern
|
||||
self.found = True
|
||||
@@ -66,22 +62,29 @@ def _is_compatible_with_hardened_malloc():
|
||||
lines = f.readlines()
|
||||
for line in lines:
|
||||
# See #2764, we need vmovdqu
|
||||
if line.startswith('flags') and ' avx ' not in line:
|
||||
# See #2959, we need vpunpckldq
|
||||
if line.startswith('flags') and ' avx2 ' not in line:
|
||||
return False
|
||||
# See #2541
|
||||
if line.startswith('Features') and ' lrcpc ' not in line:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_env(required_secrets=[], log_filters=[], log_file=None):
|
||||
if log_filters:
|
||||
sys.stdout = LogFilter(sys.stdout, log_filters, log_file)
|
||||
sys.stderr = LogFilter(sys.stderr, log_filters, log_file)
|
||||
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING'))
|
||||
|
||||
if 'LD_PRELOAD' in os.environ and not _is_compatible_with_hardened_malloc():
|
||||
log.warning('Disabling hardened-malloc on this CPU')
|
||||
del os.environ['LD_PRELOAD']
|
||||
def sigterm_handler(_signo, _stack_frame):
|
||||
log.critical("Received SIGTERM, terminating.")
|
||||
sys.exit(143)
|
||||
|
||||
def set_env(required_secrets=[], log_filters=[]):
|
||||
if log_filters:
|
||||
sys.stdout = LogFilter(sys.stdout, log_filters)
|
||||
sys.stderr = LogFilter(sys.stderr, log_filters)
|
||||
log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING'))
|
||||
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||
|
||||
if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc():
|
||||
log.warning('Your CPU has Advanced Vector Extensions available, we recommend you enable hardened-malloc earlier in the boot process by adding LD_PRELOAD=/usr/lib/libhardened_malloc.so to your mailu.env')
|
||||
os.environ['LD_PRELOAD'] = '/usr/lib/libhardened_malloc.so'
|
||||
|
||||
""" This will set all the environment variables and retains only the secrets we need """
|
||||
if 'SECRET_KEY_FILE' in os.environ:
|
||||
@@ -114,3 +117,24 @@ def drop_privs_to(username='mailu'):
|
||||
os.setgid(pwnam.pw_gid)
|
||||
os.setuid(pwnam.pw_uid)
|
||||
os.environ['HOME'] = pwnam.pw_dir
|
||||
|
||||
# forwards text lines from src to dst in an infinite loop
|
||||
def forward_text_lines(src, dst):
|
||||
while True:
|
||||
current_line = src.readline()
|
||||
dst.write(current_line)
|
||||
|
||||
|
||||
# runs a process and passes its standard/error output to the standard/error output of the current python script
|
||||
def run_process_and_forward_output(cmd):
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
stdout_thread = threading.Thread(target=forward_text_lines, args=(process.stdout, sys.stdout))
|
||||
stdout_thread.daemon = True
|
||||
stdout_thread.start()
|
||||
|
||||
stderr_thread = threading.Thread(target=forward_text_lines, args=(process.stderr, sys.stderr))
|
||||
stderr_thread.daemon = True
|
||||
stderr_thread.start()
|
||||
|
||||
process.wait()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pip==22.3.1
|
||||
setuptools==65.6.3
|
||||
wheel==0.38.4
|
||||
pip==23.3.1
|
||||
setuptools==68.2.2
|
||||
wheel==0.41.3
|
||||
|
||||
@@ -1,86 +1,93 @@
|
||||
aiodns==3.0.0
|
||||
aiohttp==3.8.5
|
||||
aiosignal==1.2.0
|
||||
alembic==1.8.1
|
||||
async-timeout==4.0.2
|
||||
attrs==22.1.0
|
||||
Babel==2.11.0
|
||||
aiodns==3.1.1
|
||||
aiohttp==3.8.6
|
||||
aiosignal==1.3.1
|
||||
alembic==1.12.1
|
||||
aniso8601==9.0.1
|
||||
async-timeout==4.0.3
|
||||
attrs==23.1.0
|
||||
Babel==2.13.1
|
||||
bcrypt==4.0.1
|
||||
blinker==1.5
|
||||
blinker==1.6.3
|
||||
certifi==2023.7.22
|
||||
cffi==1.15.1
|
||||
charset-normalizer==2.1.1
|
||||
click==8.1.3
|
||||
cffi==1.16.0
|
||||
charset-normalizer==3.3.1
|
||||
click==8.1.7
|
||||
colorclass==2.2.2
|
||||
cryptography==41.0.3
|
||||
decorator==5.1.1
|
||||
cryptography==41.0.5
|
||||
defusedxml==0.7.1
|
||||
Deprecated==1.2.13
|
||||
dnspython==2.2.1
|
||||
dominate==2.7.0
|
||||
Deprecated==1.2.14
|
||||
dnspython==2.4.2
|
||||
dominate==2.8.0
|
||||
easygui==0.98.3
|
||||
email-validator==1.3.0
|
||||
Flask==2.2.5
|
||||
Flask-Babel==2.0.0
|
||||
email-validator==2.1.0.post1
|
||||
Flask==2.3.3
|
||||
flask-babel==4.0.0
|
||||
Flask-Bootstrap==3.3.7.1
|
||||
Flask-DebugToolbar==0.13.1
|
||||
Flask-Login==0.6.2
|
||||
flask-marshmallow==0.14.0
|
||||
Flask-Migrate==3.1.0
|
||||
Flask-RESTX==1.0.5
|
||||
#Flask-DebugToolbar is not compatible with Flask 2.3.3+
|
||||
#Flask-DebugToolbar==0.13.1
|
||||
Flask-Login==0.6.3
|
||||
flask-marshmallow==0.15.0
|
||||
Flask-Migrate==4.0.5
|
||||
flask-restx==1.1.0
|
||||
Flask-SQLAlchemy==2.5.1
|
||||
Flask-WTF==1.0.1
|
||||
frozenlist==1.3.1
|
||||
greenlet==2.0.0
|
||||
gunicorn==20.1.0
|
||||
# >2.5.1 bug with parsing models.py. Could otherwise be 3.0.5
|
||||
Flask-WTF==1.2.1
|
||||
frozenlist==1.4.0
|
||||
greenlet==3.0.1
|
||||
gunicorn==21.2.0
|
||||
idna==3.4
|
||||
importlib-resources==6.1.0
|
||||
infinity==1.5
|
||||
intervals==0.9.2
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
limits==2.7.1
|
||||
Mako==1.2.3
|
||||
MarkupSafe==2.1.1
|
||||
marshmallow==3.18.0
|
||||
marshmallow-sqlalchemy==0.28.1
|
||||
jsonschema==4.19.2
|
||||
jsonschema-specifications==2023.7.1
|
||||
limits==3.6.0
|
||||
Mako==1.2.4
|
||||
MarkupSafe==2.1.3
|
||||
marshmallow==3.20.1
|
||||
marshmallow-sqlalchemy==0.29
|
||||
msoffcrypto-tool==5.1.1
|
||||
multidict==6.0.2
|
||||
mysql-connector-python==8.0.32
|
||||
multidict==6.0.4
|
||||
mysql-connector-python==8.2.0
|
||||
olefile==0.46
|
||||
oletools==0.60.1
|
||||
packaging==21.3
|
||||
packaging==23.2
|
||||
passlib==1.7.4
|
||||
pcodedmp==1.2.6
|
||||
podop @ file:///app/libs/podop
|
||||
postfix-mta-sts-resolver==1.1.4
|
||||
protobuf==3.20.2
|
||||
psycopg2-binary==2.9.5
|
||||
pycares==4.2.2
|
||||
postfix-mta-sts-resolver==1.4.0
|
||||
protobuf==4.21.12
|
||||
psycopg2-binary==2.9.9
|
||||
pycares==4.4.0
|
||||
pycparser==2.21
|
||||
Pygments==2.15.0
|
||||
Pygments==2.16.1
|
||||
pyparsing==2.4.7
|
||||
python-dateutil==2.8.2
|
||||
python-magic==0.4.27
|
||||
pytz==2022.6
|
||||
pytz==2023.3.post1
|
||||
PyYAML==6.0.1
|
||||
Radicale==3.1.8
|
||||
redis==4.4.4
|
||||
redis==5.0.1
|
||||
referencing==0.30.2
|
||||
requests==2.31.0
|
||||
rpds-py==0.10.6
|
||||
six==1.16.0
|
||||
socrate @ file:///app/libs/socrate
|
||||
SQLAlchemy==1.4.42
|
||||
SQLAlchemy==1.4.50
|
||||
srslib==0.1.4
|
||||
tabulate==0.9.0
|
||||
tenacity==8.1.0
|
||||
typing_extensions==4.4.0
|
||||
urllib3==1.26.12
|
||||
validators==0.20.0
|
||||
tenacity==8.2.3
|
||||
typing_extensions==4.8.0
|
||||
urllib3==2.0.7
|
||||
validators==0.22.0
|
||||
visitor==0.1.3
|
||||
vobject==0.9.6.1
|
||||
watchdog==2.1.9
|
||||
Werkzeug==2.2.3
|
||||
wrapt==1.14.1
|
||||
WTForms==3.0.1
|
||||
watchdog==3.0.0
|
||||
Werkzeug===2.3.7
|
||||
wrapt==1.15.0
|
||||
WTForms==3.1.1
|
||||
WTForms-Components==0.10.5
|
||||
xmltodict==0.13.0
|
||||
yarl==1.8.1
|
||||
yarl==1.9.2
|
||||
|
||||
@@ -7,7 +7,8 @@ ARG VERSION
|
||||
LABEL version=$VERSION
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; apk add --no-cache dovecot dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond rspamd-client xapian-core \
|
||||
; echo -e 'http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories \
|
||||
; apk add --no-cache 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond rspamd-client dovecot-fts-flatcurve \
|
||||
; mkdir /var/lib/dovecot
|
||||
|
||||
COPY conf/ /conf/
|
||||
|
||||
@@ -37,7 +37,7 @@ mail_plugins = $mail_plugins quota quota_clone{{ ' ' }}
|
||||
zlib{{ ' ' }}
|
||||
{%- endif %}
|
||||
{%- if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] -%}
|
||||
fts fts_xapian
|
||||
fts fts_flatcurve
|
||||
{%- endif %}
|
||||
default_vsz_limit = 2GB
|
||||
|
||||
@@ -57,11 +57,21 @@ plugin {
|
||||
quota_clone_dict = proxy:/tmp/podop.socket:quota
|
||||
|
||||
{% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %}
|
||||
fts = xapian
|
||||
fts_xapian = partial=2 full=30
|
||||
fts = flatcurve
|
||||
fts_index_timeout = 50s
|
||||
fts_languages = {% if FULL_TEXT_SEARCH %}{{ FULL_TEXT_SEARCH.split(",") | join(" ") }}{% else %}en{% endif %}
|
||||
fts_tokenizers = generic email-address
|
||||
fts_autoindex = yes
|
||||
fts_enforced = yes
|
||||
fts_autoindex_exclude = \Trash
|
||||
fts_autoindex_exclude1 = \Junk
|
||||
fts_filters = normalizer-icu snowball stopwords
|
||||
fts_filters_en = lowercase snowball english-possessive stopwords
|
||||
fts_filters_fr = lowercase snowball contractions stopwords
|
||||
fts_header_excludes = Received DKIM-* ARC-* X-* x-* Comments Delivered-To Return-Path Authentication-Results Message-ID References In-Reply-To Thread-* Accept-Language Content-* MIME-Version
|
||||
{% if FULL_TEXT_SEARCH_ATTACHMENTS %}
|
||||
fts_tika = http://{{ FTS_ATTACHMENTS_ADDRESS }}:9998/tika/
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if COMPRESSION in [ 'gz', 'bz2', 'lz4', 'zstd' ] %}
|
||||
@@ -73,6 +83,12 @@ plugin {
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
service indexer-worker {
|
||||
executable = /bin/nice -n 10 /usr/libexec/dovecot/indexer-worker
|
||||
# TODO: maybe MAXPROC? I guess it depends on how much RAM is available
|
||||
process_limit = 1
|
||||
}
|
||||
|
||||
###############
|
||||
# Authentication
|
||||
###############
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
import os
|
||||
import glob
|
||||
import multiprocessing
|
||||
import logging as log
|
||||
import sys
|
||||
|
||||
from podop import run_server
|
||||
from socrate import system, conf
|
||||
|
||||
system.set_env(log_filters=r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$')
|
||||
system.set_env(log_filters=[r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$'])
|
||||
|
||||
def start_podop():
|
||||
system.drop_privs_to('mail')
|
||||
@@ -35,4 +33,5 @@ os.system("chown mail:mail /mail")
|
||||
os.system("chown -R mail:mail /var/lib/dovecot /conf")
|
||||
|
||||
multiprocessing.Process(target=start_podop).start()
|
||||
os.system("dovecot -c /etc/dovecot/dovecot.conf -F")
|
||||
cmd = ['/usr/sbin/dovecot', '-c', '/etc/dovecot/dovecot.conf', '-F']
|
||||
system.run_process_and_forward_output(cmd)
|
||||
|
||||
@@ -17,7 +17,8 @@ ARG VERSION
|
||||
LABEL version=$VERSION
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; apk add --no-cache certbot nginx nginx-mod-http-brotli nginx-mod-mail openssl dovecot-lua dovecot-pigeonhole-plugin dovecot-lmtpd dovecot-pop3d dovecot-submissiond
|
||||
; apk add --no-cache certbot nginx nginx-mod-http-brotli nginx-mod-mail openssl \
|
||||
; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main dovecot-lua dovecot-pigeonhole-plugin 'dovecot-lmtpd<2.4' dovecot-pop3d dovecot-submissiond
|
||||
|
||||
COPY conf/ /conf/
|
||||
COPY --from=static /static/ /static/
|
||||
|
||||
@@ -47,7 +47,7 @@ time.sleep(5)
|
||||
|
||||
class MyRequestHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/testing':
|
||||
if self.path == '/.well-known/acme-challenge/testing':
|
||||
self.send_response(204)
|
||||
else:
|
||||
self.send_response(404)
|
||||
@@ -55,19 +55,19 @@ class MyRequestHandler(SimpleHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
|
||||
def serve_one_request():
|
||||
with HTTPServer(("0.0.0.0", 8008), MyRequestHandler) as server:
|
||||
with HTTPServer(("127.0.0.1", 8008), MyRequestHandler) as server:
|
||||
server.handle_request()
|
||||
|
||||
# Run certbot every day
|
||||
while True:
|
||||
while True:
|
||||
hostname = os.environ['HOSTNAMES'].split(' ')[0]
|
||||
hostname = os.environ['HOSTNAMES'].split(',')[0]
|
||||
target = f'http://{hostname}/.well-known/acme-challenge/testing'
|
||||
thread = Thread(target=serve_one_request)
|
||||
thread.start()
|
||||
r = requests.get(target)
|
||||
if r.status_code != 204:
|
||||
log.error(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.")
|
||||
log.critical(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.")
|
||||
time.sleep(5)
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import subprocess
|
||||
from socrate import system
|
||||
|
||||
system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25,110,143,587,465,993,995)$')
|
||||
system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25|110|143|587|465|993|995)$')
|
||||
|
||||
# Check if a stale pid file exists
|
||||
if os.path.exists("/var/run/nginx.pid"):
|
||||
@@ -17,4 +17,5 @@ elif os.environ["TLS_FLAVOR"] in [ "mail", "cert" ]:
|
||||
|
||||
subprocess.call(["/config.py"])
|
||||
os.system("dovecot -c /etc/dovecot/proxy.conf")
|
||||
os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"])
|
||||
cmd = ['/usr/sbin/nginx', '-g', 'daemon off;']
|
||||
system.run_process_and_forward_output(cmd)
|
||||
|
||||
@@ -6,11 +6,17 @@ FROM base
|
||||
ARG VERSION=local
|
||||
LABEL version=$VERSION
|
||||
|
||||
ARG OLEFY_SCRIPT=https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py
|
||||
ARG OLEFY_SHA256=1f5aa58b78ca7917350135b4425e5ed4d580c7051aabed1952c6afd12d0345a0
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; apk add --no-cache netcat-openbsd libmagic libffi \
|
||||
; curl -sLo olefy.py https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py \
|
||||
; curl -sLo olefy.py $OLEFY_SCRIPT \
|
||||
; echo "$OLEFY_SHA256 olefy.py" |sha256sum -c \
|
||||
; chmod 755 olefy.py
|
||||
|
||||
COPY start.py /
|
||||
|
||||
RUN echo $VERSION >/version
|
||||
|
||||
HEALTHCHECK --start-period=60s CMD echo PING|nc -q1 127.0.0.1 11343|grep "PONG"
|
||||
@@ -28,4 +34,4 @@ ENV \
|
||||
OLEFY_DEL_TMP="1" \
|
||||
OLEFY_DEL_TMP_FAILED="1"
|
||||
|
||||
CMD /app/olefy.py
|
||||
CMD /start.py
|
||||
|
||||
8
core/oletools/start.py
Executable file
8
core/oletools/start.py
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from socrate import system
|
||||
|
||||
system.set_env()
|
||||
|
||||
with open('/app/olefy.py') as olefy:
|
||||
exec(olefy.read())
|
||||
@@ -13,8 +13,8 @@ from socrate import system, conf
|
||||
system.set_env(log_filters=[
|
||||
r'(dis)?connect from localhost\[(\:\:1|127\.0\.0\.1)\]( quit=1 commands=1)?$',
|
||||
r'haproxy read\: short protocol header\: QUIT$',
|
||||
r'discarding EHLO keywords\: PIPELINING$',
|
||||
], log_file=os.environ.get('POSTFIX_LOG_FILE'))
|
||||
r'discarding EHLO keywords\: PIPELINING$'
|
||||
])
|
||||
|
||||
os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid")
|
||||
|
||||
@@ -100,4 +100,5 @@ os.system("/usr/libexec/postfix/post-install meta_directory=/etc/postfix create-
|
||||
# Before starting postfix, we need to check permissions on /queue
|
||||
# in the event that postfix,postdrop id have changed
|
||||
os.system("postfix set-permissions")
|
||||
os.system("postfix start-fg")
|
||||
cmd = ['postfix', 'start-fg']
|
||||
system.run_process_and_forward_output(cmd)
|
||||
|
||||
@@ -4,7 +4,7 @@ clamav {
|
||||
symbol = "CLAM_VIRUS";
|
||||
type = "clamav";
|
||||
servers = "{{ ANTIVIRUS_ADDRESS }}:3310";
|
||||
timeout = 5;
|
||||
timeout = 60;
|
||||
retransmits = 10;
|
||||
{% if ANTIVIRUS_ACTION|default('discard') == 'reject' %}
|
||||
action = "reject"
|
||||
|
||||
@@ -7,8 +7,8 @@ oletools {
|
||||
scan_mime_parts = true;
|
||||
extended = true;
|
||||
max_size = 3145728;
|
||||
timeout = 20.0;
|
||||
retransmits = 1;
|
||||
timeout = 60.0;
|
||||
retransmits = 10;
|
||||
|
||||
patterns {
|
||||
OLETOOLS_MACRO_FOUND= '^.....M..$';
|
||||
|
||||
@@ -14,5 +14,15 @@ rules {
|
||||
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & BLACKLIST_ANTISPOOF";
|
||||
message = "Rejected (anti-spoofing: auth-failed)";
|
||||
}
|
||||
ANTIVIRUS_FLAGGED {
|
||||
action = "reject";
|
||||
expression = "CLAM_VIRUS | OLETOOLS_MACRO_MRAPTOR | OLETOOLS_MACRO_SUSPICIOUS";
|
||||
message = "Rejected (dangerous/malicious code detected)";
|
||||
}
|
||||
ANTIVIRUS_FAILED {
|
||||
action = "soft reject";
|
||||
expression = "CLAM_VIRUS_FAIL | OLETOOLS_FAIL";
|
||||
message = "Please retry later (anti-virus/oletools not ready)";
|
||||
}
|
||||
}
|
||||
.include(try=true,priority=1,duplicate=merge) "/overrides/force_actions.conf"
|
||||
|
||||
@@ -16,7 +16,8 @@ env = system.set_env()
|
||||
config_files = []
|
||||
for rspamd_file in glob.glob("/conf/*"):
|
||||
conf.jinja(rspamd_file, env, os.path.join("/etc/rspamd/local.d", os.path.basename(rspamd_file)))
|
||||
config_files.append(os.path.basename(rspamd_file))
|
||||
if rspamd_file != '/conf/forbidden_file_extension.map':
|
||||
config_files.append(os.path.basename(rspamd_file))
|
||||
|
||||
for override_file in glob.glob("/overrides/*"):
|
||||
if os.path.basename(override_file) not in config_files:
|
||||
@@ -37,4 +38,4 @@ while True:
|
||||
os.system("mkdir -m 755 -p /run/rspamd")
|
||||
os.system("chown rspamd:rspamd /run/rspamd")
|
||||
os.system("find /var/lib/rspamd | grep -v /filter | xargs -n1 chown rspamd:rspamd")
|
||||
os.execv("/usr/sbin/rspamd", ["rspamd", "-f", "-u", "rspamd", "-g", "rspamd"])
|
||||
os.execv("/usr/bin/rspamd", ["rspamd", "-f", "-u", "rspamd", "-g", "rspamd"])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Convert .rst files to .html in temporary build container
|
||||
FROM python:3.8-alpine3.14 AS build
|
||||
FROM python:3.12.0-alpine3.18 AS build
|
||||
|
||||
ARG version=master
|
||||
ENV VERSION=$version
|
||||
@@ -16,7 +16,7 @@ RUN apk add --no-cache --virtual .build-deps \
|
||||
|
||||
|
||||
# Build nginx deployment image including generated html
|
||||
FROM nginx:1.21-alpine
|
||||
FROM nginx:1.25.3-alpine
|
||||
|
||||
ARG version=master
|
||||
ARG pinned_version=master
|
||||
|
||||
@@ -33,7 +33,7 @@ Rspamd rejects non-compliant email messages and email messages that contain viru
|
||||
|
||||
* In the administration web interface, under settings under Antispam 'Enable spam filter' must be ticked. If this option is disabled, then all email messages will automatically go to the inbox folder. Except for email messages with a score of 15 or higher, as these email messages are rejected by Rspamd.
|
||||
|
||||
* In the administration web interface, under settings under Antispam, the user defined spam filter tolerance must be configured. The default value is 80%. The lower the spam filter tolerance, the more false positives (ham classified as spam). The user can change this setting to finetune when an email message is classified as spam.
|
||||
* In the administration web interface, under settings under Antispam, the user defined spam filter tolerance must be configured. The default value is 80%. The lower the spam filter tolerance, the more false positives (ham classified as spam). The user can change this setting to fine-tune when an email message is classified as spam.
|
||||
|
||||
* Dovecot extracts the X-Spam-Level email header from the email message and converts the spam score (0 - 15) to a 0 - 100 percent scale. This spam score is compared with the user defined spam filter tolerance. If the spam score is lower than the user defined spam filter tolerance, then the email message is accepted. In logic:
|
||||
|
||||
@@ -110,7 +110,7 @@ The following steps have to be taken to configure an additional symbol (rule) th
|
||||
|
||||
* no action: allow message. The email message will be allowed without a spam score being added in the mail header. This can be used for creating a whitelist filter.
|
||||
|
||||
* soft reject: temporarily delay message (this is used, for instance, to greylist or ratelimit messages)
|
||||
* soft reject: temporarily delay message (this is used, for instance, to greylist or rate-limit messages)
|
||||
|
||||
To move an email message to the Junk (Spam) folder, a score of 15 can be used in combination with the action "add header".
|
||||
The above example configuration will reject all emails send from domains that are listed in '/etc/rspamd/override.d/blacklist.inc'.
|
||||
@@ -158,7 +158,7 @@ For more information on using the multimap filter see the official `multimap doc
|
||||
Can I change the list of authorized file attachments?
|
||||
-----------------------------------------------------
|
||||
|
||||
Mailu rejects emails with file attachements it deems to be "executable" or otherwise dangerous. If you would like to tweak the block list, you can do so using the following commands:
|
||||
Mailu rejects emails with file attachments it deems to be "executable" or otherwise dangerous. If you would like to tweak the block list, you can do so using the following commands:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
@@ -152,3 +152,8 @@ REJECT_UNLISTED_RECIPIENT=
|
||||
|
||||
# Log level threshold in start.py (value: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET)
|
||||
LOG_LEVEL=WARNING
|
||||
|
||||
# If your CPU supports Advanced Vector Extensions
|
||||
# (AVX2 on x86_64, lrcpc on ARM64), you should consider enabling
|
||||
# hardened-malloc earlier by uncommenting this
|
||||
# LD_PRELOAD=/usr/lib/libhardened_malloc.so
|
||||
|
||||
@@ -43,7 +43,7 @@ make sure that you either:
|
||||
|
||||
- setup a root *ext4* partition,
|
||||
- or setup a root *btrfs* partition,
|
||||
- or leave enough unpartitionned space for a dedicated *ext4* or *btrfs*
|
||||
- or leave enough unpartitioned space for a dedicated *ext4* or *btrfs*
|
||||
partition.
|
||||
|
||||
If you chose to create a dedicated partition, simply mount it to
|
||||
@@ -74,7 +74,7 @@ default Debian install:
|
||||
apt-get autoremove --purge exim4 exim4-base
|
||||
|
||||
|
||||
Finally, Docker relies heavily on ``iptables`` for port forwardings. You
|
||||
Finally, Docker relies heavily on ``iptables`` for port forwarding. You
|
||||
should use ``iptables-persistent`` (or any equivalent tool on other
|
||||
systems) for managing persistent rules. If you were brave enough to switch to
|
||||
``nftables``, you will have to rollback until official support is released
|
||||
|
||||
@@ -76,6 +76,15 @@ Review configuration variables
|
||||
After downloading the files, open ``mailu.env`` and review the variable settings.
|
||||
Make sure to read the comments in the file and instructions from the :ref:`common_cfg` page.
|
||||
|
||||
If your CPU supports Advanced Vector Extensions (AVX2 on x86_64, lrcpc on ARM64), you should
|
||||
consider enabling hardened-malloc earlier in the boot process by adding the following to
|
||||
your mailu.env:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
LD_PRELOAD=/usr/lib/libhardened_malloc.so
|
||||
|
||||
|
||||
Finish setting up TLS
|
||||
---------------------
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ accounts for a specific IP subnet as defined in
|
||||
|
||||
The ``AUTH_RATELIMIT_USER`` (default: 50/day) holds a security setting for fighting
|
||||
attackers that attempt to guess a user's password (typically using a password
|
||||
bruteforce attack). The value defines the limit of distinct authentication attempts
|
||||
allowed for any given account within a specific timeframe. Multiple attempts for the
|
||||
brute-force attack). The value defines the limit of distinct authentication attempts
|
||||
allowed for any given account within a specific time-frame. Multiple attempts for the
|
||||
same account with the same password only counts for one.
|
||||
|
||||
The ``AUTH_RATELIMIT_EXEMPTION_LENGTH`` (default: 86400) is the number of seconds
|
||||
@@ -104,7 +104,7 @@ by setting ``INBOUND_TLS_ENFORCE`` to ``True``. Please note that this is forbidd
|
||||
internet facing hosts according to e.g. `RFC 3207`_ , because this prevents MTAs without STARTTLS
|
||||
support or e.g. mismatching TLS versions to deliver emails to Mailu.
|
||||
|
||||
The ``SCAN_MACROS`` (default: True) setting controls whether Mailu will endavour
|
||||
The ``SCAN_MACROS`` (default: True) setting controls whether Mailu will endeavor
|
||||
to reject emails containing documents with malicious macros. Under the hood, it uses
|
||||
`mraptor from oletools`_ to determine whether a macro is malicious or not.
|
||||
|
||||
@@ -133,12 +133,15 @@ later classify incoming mail based on the custom part.
|
||||
The ``DMARC_RUA`` and ``DMARC_RUF`` are DMARC protocol specific values. They hold
|
||||
the localpart for DMARC rua and ruf email addresses.
|
||||
|
||||
Full-text search is enabled for IMAP is enabled by default. This feature can be disabled
|
||||
(e.g. for performance reasons) by setting the optional variable ``FULL_TEXT_SEARCH`` to ``off``.
|
||||
The ``FULL_TEXT_SEARCH`` variable (default: 'en') is a comma separated list of
|
||||
language codes as defined on `fts_languages`_. This feature can be disabled
|
||||
(e.g. for performance reasons) by setting the variable to ``off``.
|
||||
|
||||
You can set a global ``DEFAULT_QUOTA`` to be used for mailboxes when the domain has
|
||||
no specific quota configured.
|
||||
|
||||
.. _`fts_languages`: https://doc.dovecot.org/settings/plugin/fts-plugin/#fts-languages
|
||||
|
||||
.. _web_settings:
|
||||
|
||||
Web settings
|
||||
@@ -167,11 +170,11 @@ in the admin interface, while ``SITENAME`` is a customization option for
|
||||
every Web interface.
|
||||
|
||||
- ``LOGO_BACKGROUND`` sets a custom background colour for the brand logo
|
||||
in the topleft of the main admin interface.
|
||||
in the top-left of the main admin interface.
|
||||
For a list of colour codes refer to this page of `w3schools`_.
|
||||
|
||||
- ``LOGO_URL`` sets a URL for a custom logo. This logo replaces the Mailu
|
||||
logo in the topleft of the main admin interface.
|
||||
logo in the top-left of the main admin interface.
|
||||
|
||||
.. _`w3schools`: https://www.w3schools.com/cssref/css_colors.asp
|
||||
|
||||
@@ -213,7 +216,10 @@ Depending on your particular deployment you most probably will want to change th
|
||||
Advanced settings
|
||||
-----------------
|
||||
|
||||
The ``API_TOKEN`` (default: None) configures the authentication token.
|
||||
|
||||
The ``AUTH_REQUIRE_TOKENS`` (default: False) setting controls whether thick clients can authenticate using passwords or whether they are forced to use tokens/application specific passwords.
|
||||
|
||||
The ``API_TOKEN`` (default: None) setting configures the authentication token.
|
||||
This token must be passed as request header to the API as authentication token.
|
||||
This is a mandatory setting for using the RESTful API.
|
||||
|
||||
@@ -375,22 +381,6 @@ To disable all plugins just set ``ROUNDCUBE_PLUGINS`` to ``mailu``.
|
||||
|
||||
To configure a plugin add php files named ``*.inc.php`` to roundcube's :ref:`override section <override-label>`.
|
||||
|
||||
Mail log settings
|
||||
-----------------
|
||||
|
||||
By default, all services log directly to stdout/stderr. Logs can be collected by any docker log processing solution.
|
||||
|
||||
Postfix writes the logs to a syslog server which logs to stdout. This is used to filter
|
||||
out messages from the healthcheck. In some situations, a separate mail log is required
|
||||
(e.g. for legal reasons). The syslog server can be configured to write log files to a volume.
|
||||
It can be configured with the following option:
|
||||
|
||||
- ``POSTFIX_LOG_FILE``: The file to log the mail log to. When enabled, the syslog server will also log to stdout.
|
||||
|
||||
When ``POSTFIX_LOG_FILE`` is enabled, the logrotate program will automatically rotate the
|
||||
logs every week and keep 52 logs. To override the logrotate configuration, create the file logrotate.conf
|
||||
with the desired configuration in the :ref:`Postfix overrides folder<override-label>`.
|
||||
|
||||
.. _header_authentication:
|
||||
|
||||
Header authentication using an external proxy
|
||||
|
||||
@@ -93,7 +93,7 @@ Configuration files should be compiled at runtime by the container `start.py`
|
||||
script and all conditional syntax should be handled using Jinja logic.
|
||||
|
||||
The `socrate` Python package should include relevant functions for container
|
||||
lifecycle management.
|
||||
life-cycle management.
|
||||
|
||||
Anything that is not static, i.e. able to change at runtime, either due to
|
||||
configuration in the admin UI or user behavior, should take advantage of the
|
||||
|
||||
@@ -8,7 +8,7 @@ Mailu uses Babel for internationalization and localization. Before any
|
||||
of your work is merged, you must make sure that your strings are internationalized
|
||||
using Babel.
|
||||
|
||||
If you used ``_``, ``trans`` blocks and other Babel syntaxes in your code, run the
|
||||
If you used ``_``, ``trans`` blocks and other Babel syntax in your code, run the
|
||||
following command to update the POT file:
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -26,7 +26,7 @@ Please resolve fuzzy strings to the best of your knowledge.
|
||||
Update information files
|
||||
------------------------
|
||||
|
||||
If you added a feature or fixed a bug or committed anything that is worth mentionning
|
||||
If you added a feature or fixed a bug or committed anything that is worth mentioning
|
||||
for the next upgrade, add it in the ``CHANGELOG.md`` file.
|
||||
|
||||
Also, if you would like to be mentioned by name or add a comment in ``AUTHORS.md``,
|
||||
|
||||
@@ -19,8 +19,8 @@ This is a community project, thus commits should be readable enough for any of
|
||||
the contributors to guess the content by simply reading the comment or find a
|
||||
proper commit when one knows what they are looking for.
|
||||
|
||||
Usual standards remain: write english comments, single line short comments and
|
||||
additional multiline if required (keep in mind that the most important piece
|
||||
Usual standards remain: write English comments, single line short comments and
|
||||
additional multi-line if required (keep in mind that the most important piece
|
||||
of information should fit in the first line).
|
||||
|
||||
Branches
|
||||
|
||||
@@ -25,9 +25,9 @@ To switch to a different database back-end:
|
||||
|
||||
1. Drop into a shell inside the admin container as you'll need to execute multiple commands. E.g. `docker exec -i $(docker compose ps -q admin) bash`
|
||||
|
||||
2. Initialize the new database backend: `flask mailu db init`
|
||||
2. Initialize the new database back-end: `flask mailu db init`
|
||||
|
||||
3. Migrate the new database backend to the current state: `flask mailu db upgrade`
|
||||
3. Migrate the new database back-end to the current state: `flask mailu db upgrade`
|
||||
|
||||
4. Import the configuration export: `flask mailu config-import -v < /data/mail-config.yml`
|
||||
|
||||
@@ -216,4 +216,4 @@ Optionally you can remove left-over files which were used by the old database:
|
||||
|
||||
.. note::
|
||||
Roundcube does not offer a migration tool for moving from SQLite to PostgreSQL.
|
||||
In case roundcube is used, the Mailu setup utility can be used to specify SQLite for the roundcube database backend.
|
||||
In case roundcube is used, the Mailu setup utility can be used to specify SQLite for the roundcube database back-end.
|
||||
|
||||
@@ -35,7 +35,7 @@ Connecting to the server
|
||||
* Webmail : https://test.mailu.io/webmail/
|
||||
* Admin UI : https://test.mailu.io/admin/
|
||||
* Admin login : ``admin@test.mailu.io``
|
||||
* Admin password : ``letmein``
|
||||
* Admin password : ``Mailu+Demo@test.mailu.io`` (remove + and @test.mailu.io to get the correct password).
|
||||
* RESTful API: https://test.mailu.io/api
|
||||
* API token: ``Bearer APITokenForMailu``
|
||||
|
||||
|
||||
101
docs/faq.rst
101
docs/faq.rst
@@ -11,8 +11,7 @@ Where to ask questions?
|
||||
|
||||
First, please read this FAQ to check if your question is listed here.
|
||||
Simple questions are best asked in our `Matrix`_ room.
|
||||
For more complex questions, you can always open a `new issue`_ on GitHub.
|
||||
We actively monitor the issues list.
|
||||
For more complex questions, you can always open a `new discussion`_ on GitHub.
|
||||
|
||||
|
||||
My installation is broken!
|
||||
@@ -33,16 +32,17 @@ I want a new feature or enhancement!
|
||||
|
||||
Great! We are always open for suggestions. We currently maintain two tags:
|
||||
|
||||
- `Enhancement issues`_: Typically used for optimization of features in the project.
|
||||
- `Feature request issues`_: For implementing new functionality,
|
||||
- ``type/enhancement``: Typically used for optimization of features in the project.
|
||||
- ``type/feature``: For implementing new functionality,
|
||||
plugins and applications.
|
||||
|
||||
Please check if your idea (or something similar) is already mentioned there.
|
||||
Feature requests are discussed on the discussion page of the project (see `feature requests`_).
|
||||
Please check if your idea (or something similar) is already mentioned on the project.
|
||||
If there is one open, you can choose to vote with a thumbs up, so we can
|
||||
estimate the popular demand. Please refrain from writing comments like
|
||||
*"me too"* as it clobbers the actual discussion.
|
||||
|
||||
If you can't find anything similar, you can open a `new issue`_.
|
||||
If you can't find anything similar, you can open a `new feature request`_.
|
||||
Please also share (where applicable):
|
||||
|
||||
- Use case: how does this improve the project?
|
||||
@@ -89,8 +89,9 @@ Please click the |sponsor| button on top of our GitHub Page for current possibil
|
||||
.. _`Matrix`: https://matrix.to/#/#mailu:tedomum.net
|
||||
.. _`open issues`: https://github.com/Mailu/Mailu/issues
|
||||
.. _`new issue`: https://github.com/Mailu/Mailu/issues/new
|
||||
.. _`Enhancement issues`: https://github.com/Mailu/Mailu/issues?q=is%3Aissue+is%3Aopen+label%3Atype%2Fenhancement
|
||||
.. _`Feature request issues`: https://github.com/Mailu/Mailu/issues?q=is%3Aopen+is%3Aissue+label%3Atype%2Ffeature
|
||||
.. _`new discussion`: https://github.com/Mailu/Mailu/discussions/categories/user-support
|
||||
.. _`feature requests`: https://github.com/Mailu/Mailu/discussions/categories/feature-requests-ideas
|
||||
.. _`new feature request`: https://github.com/Mailu/Mailu/discussions/new?category=feature-requests-ideas
|
||||
.. _`GitHub`: https://github.com/Mailu/Mailu
|
||||
.. _`Community Bridge`: https://funding.communitybridge.org/projects/mailu
|
||||
|
||||
@@ -192,8 +193,8 @@ This means it can be scaled horizontally. For more information, refer to :ref:`k
|
||||
|
||||
*Issue reference:* `165`_, `520`_.
|
||||
|
||||
How to achieve HA / failover?
|
||||
`````````````````````````````
|
||||
How to achieve HA / fail-over?
|
||||
``````````````````````````````
|
||||
|
||||
The mailboxes and databases for Mailu are kept on the host filesystem under ``$ROOT/``.
|
||||
For making the **storage** highly available, all sorts of techniques can be used:
|
||||
@@ -287,7 +288,7 @@ I want to integrate Nextcloud 15 (and newer) with Mailu
|
||||
|
||||
|
||||
If a domain name (e.g. example.com) is specified, then this makes sure that only users from this domain will be allowed to login.
|
||||
After successfull login the domain part will be stripped and the rest used as username in Nextcloud. e.g. 'username@example.com' will be 'username' in Nextcloud. Disable this behaviour by changing true (the fifth parameter) to false.
|
||||
After successful login the domain part will be stripped and the rest used as username in Nextcloud. e.g. 'username@example.com' will be 'username' in Nextcloud. Disable this behaviour by changing true (the fifth parameter) to false.
|
||||
|
||||
*Issue reference:* `575`_.
|
||||
|
||||
@@ -474,7 +475,7 @@ down and up again. A container restart is not sufficient.
|
||||
SMTP Banner from overrides/postfix.cf is ignored
|
||||
````````````````````````````````````````````````
|
||||
|
||||
Any mail related connection is proxied by nginx. Therefore the SMTP Banner is also set by nginx. Overwriting in overrides/postfix.cf does not apply.
|
||||
Any mail related connection is proxied by the front container. Therefore the SMTP Banner is also set by front container. Overwriting in overrides/postfix.cf does not apply.
|
||||
|
||||
*Issue reference:* `1368`_.
|
||||
|
||||
@@ -496,8 +497,8 @@ Re-starting the smtp container will be required for changes to take effect.
|
||||
|
||||
.. _`2213`: https://github.com/Mailu/Mailu/issues/2213
|
||||
|
||||
My emails are getting defered, what can I do?
|
||||
`````````````````````````````````````````````
|
||||
My emails are getting deferred, what can I do?
|
||||
``````````````````````````````````````````````
|
||||
|
||||
Emails are asynchronous and it's not abnormal for them to be defered sometimes. That being said, Mailu enforces secure connections where possible using DANE and MTA-STS, both of which have the potential to delay indefinitely delivery if something is misconfigured.
|
||||
|
||||
@@ -754,8 +755,8 @@ Restart the Fail2Ban service.
|
||||
Users can't change their password from webmail
|
||||
``````````````````````````````````````````````
|
||||
|
||||
All users have the abilty to login to the admin interface. Non-admin users
|
||||
have only restricted funtionality such as changing their password and the
|
||||
All users have the ability to login to the admin interface. Non-admin users
|
||||
have only restricted functionality such as changing their password and the
|
||||
spam filter weight settings.
|
||||
|
||||
*Issue reference:* `503`_.
|
||||
@@ -903,6 +904,13 @@ We have seen a fair amount of support requests related to the following:
|
||||
|
||||
.. _`netplan does not play nicely with docker`: https://github.com/Mailu/Mailu/issues/2868#issuecomment-1606014184
|
||||
|
||||
How can I use Mailu without docker?
|
||||
```````````````````````````````````
|
||||
|
||||
Running Mailu without docker is not supported. If you want to do so, you need to export an environment variable called ``I_KNOW_MY_SETUP_DOESNT_FIT_REQUIREMENTS_AND_WONT_FILE_ISSUES_WITHOUT_PATCHES`` to the ``admin`` container.
|
||||
|
||||
We welcome patches but do not have the bandwidth to test and fix issues related to your unsupported setup. If you do want to help, we welcome new maintainers: please get in touch.
|
||||
|
||||
How can I add more languages to roundcube's spellchecker?
|
||||
`````````````````````````````````````````````````````````
|
||||
|
||||
@@ -938,3 +946,64 @@ I see a lot of "Unable to lookup the TLSA record for XXX. Is the DNSSEC zone oka
|
||||
There may be multiple causes for it but if you are running docker 24.0.0, odds are you are `experiencing this docker bug`_ and the workaround is to switch to a different version of docker.
|
||||
|
||||
.. _`experiencing this docker bug`: https://github.com/Mailu/Mailu/issues/2827
|
||||
|
||||
How can I view and export the logs of a Mailu container?
|
||||
````````````````````````````````````````````````````````
|
||||
|
||||
In some situations, a separate log is required. For example a separate mail log (from postfix) could be required due to legal reasons.
|
||||
|
||||
All Mailu containers log the output to journald. The logs are written to journald with the tag:
|
||||
|
||||
| mailu-<service name>
|
||||
| where <service-name> is the name of the service in the docker-compose.yml file.
|
||||
| For example, the service running postfix is called smtp. To view the postfix logs use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
journalctl -t mailu-smtp
|
||||
|
||||
Note: ``SHIFT+G`` can be used to jump to the end of the log file. ``G`` can be used to jump back to the top of the log file.
|
||||
|
||||
To export the log files from journald to the file system, the logs could be imported into a syslog program like ``rsyslog``.
|
||||
Via ``rsyslog`` the container specific logs could be written to a separate file using a filter.
|
||||
|
||||
Below are the steps for writing the postfix (mail) logs to a log file on the file system.
|
||||
|
||||
1. Install the ``rsyslog`` package. Note: on most distributions this program is already installed.
|
||||
2. Edit ``/etc/systemd/journald.conf``.
|
||||
3. Enable ``ForwardToSyslog=yes``. Note: on most distributions this is already enabled by default. This forwards journald to syslog.
|
||||
4. ``sudo touch /var/log/postfix.log``. This step creates the mail log file.
|
||||
5. ``sudo chown syslog:syslog /var/log/postfix.log``. This provides rsyslog the permissions for accessing this file.
|
||||
6. Create a new config file in ``/etc/rsyslog.d/export-postfix.conf``
|
||||
7. Add ``:programname, contains, "mailu-smtp" /var/log/postfix.log``. This instructs rsyslog to write the logs for mailu-smtp to a log file on file system.
|
||||
8. ``sudo systemctl restart systemd-journald.service``
|
||||
9. ``sudo systemctl restart rsyslog``
|
||||
10. All messages from the smtp/postfix container are now logged to ``/var/log/postfix.log``.
|
||||
11. Rsyslog does not perform log rotation. The program (package) ``log rotate`` can be used for this task. Install the ``logrotate`` package.
|
||||
12. Modify the existing configuration file for rsyslog: ``sudo nano /etc/logrotate.d/rsyslog``
|
||||
13. Add at the top add: ``/var/log/postfix.log``. Of course you can also use your own configuration. This is just an example. A complete example for configuring log rotate is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
/var/log/postfix.log
|
||||
{
|
||||
rotate 4
|
||||
weekly
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
delaycompress
|
||||
sharedscripts
|
||||
postrotate
|
||||
/usr/lib/rsyslog/rsyslog-rotate
|
||||
endscript
|
||||
}
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
#!/bin/sh
|
||||
#/usr/lib/rsyslog/rsyslog-rotate
|
||||
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl kill -s HUP rsyslog.service
|
||||
fi
|
||||
|
||||
@@ -8,8 +8,8 @@ Mailu
|
||||
.. image:: https://img.shields.io/github/stars/mailu/mailu.svg?label=github&logo=github&maxAge=2592000
|
||||
:target: https://github.com/mailu/mailu
|
||||
|
||||
.. image:: https://img.shields.io/docker/pulls/mailu/admin.svg?label=docker&maxAge=2592000
|
||||
:target: https://hub.docker.com/u/mailu/
|
||||
.. image:: https://img.shields.io/badge/Mailu-latest_release-blue
|
||||
:target: https://github.com/Mailu/Mailu/releases
|
||||
|
||||
.. image:: https://img.shields.io/badge/matrix-%23mailu%3Atedomum.net-7bc9a4.svg
|
||||
:target: https://matrix.to/#/#mailu:tedomum.net
|
||||
@@ -23,8 +23,8 @@ popular groupware.
|
||||
|
||||
Main features include:
|
||||
|
||||
- **Standard email server**, IMAP and IMAP+, SMTP and Submission with autoconfiguration profiles for clients
|
||||
- **Advanced email features**, aliases, domain aliases, custom routing
|
||||
- **Standard email server**, IMAP and IMAP+, SMTP and Submission with auto-configuration profiles for clients
|
||||
- **Advanced email features**, aliases, domain aliases, custom routing, full-text search of email attachments
|
||||
- **Web access**, multiple Webmails and administration interface
|
||||
- **User features**, aliases, auto-reply, auto-forward, fetched accounts, managesieve
|
||||
- **Admin features**, global admins, announcements, per-domain delegation, quotas
|
||||
|
||||
517
docs/mailu-network-diagram.ipynb
Normal file
517
docs/mailu-network-diagram.ipynb
Normal file
@@ -0,0 +1,517 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/svg+xml": [
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
|
||||
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
|
||||
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
|
||||
"<!-- Generated by graphviz version 2.43.0 (0)\n",
|
||||
" -->\n",
|
||||
"<!-- Title: mailu Pages: 1 -->\n",
|
||||
"<svg width=\"706pt\" height=\"553pt\"\n",
|
||||
" viewBox=\"0.00 0.00 706.00 553.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
|
||||
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 549)\">\n",
|
||||
"<title>mailu</title>\n",
|
||||
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-549 702,-549 702,4 -4,4\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"349\" y=\"-7.8\" font-family=\"arial\" font-size=\"14.00\">Mailu</text>\n",
|
||||
"<!-- internet -->\n",
|
||||
"<g id=\"node1\" class=\"node\">\n",
|
||||
"<title>internet</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"297,-545 243,-545 243,-509 297,-509 297,-545\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"270\" y=\"-525.1\" font-family=\"arial\" font-size=\"8.00\">Internet</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front -->\n",
|
||||
"<g id=\"node2\" class=\"node\">\n",
|
||||
"<title>front</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"364,-464 310,-464 310,-428 364,-428 364,-464\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"337\" y=\"-444.1\" font-family=\"arial\" font-size=\"8.00\">Front</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge1\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.81,-521.99C180.56,-512.79 33,-491 33,-491 33,-491 33,-482 33,-482 33,-482 218.14,-460.68 299.46,-451.32\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.22,-454.76 309.75,-450.14 299.42,-447.8 300.22,-454.76\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"46.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">80/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge2\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.81,-520.91C190.94,-511.2 83,-491 83,-491 83,-491 83,-482 83,-482 83,-482 228.93,-461.89 299.56,-452.16\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.39,-455.58 309.82,-450.75 299.43,-448.64 300.39,-455.58\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"99\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">443/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge3\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.97,-518.83C204.35,-508.59 138,-491 138,-491 138,-491 138,-482 138,-482 138,-482 241.91,-463.72 299.56,-453.59\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.5,-456.97 309.75,-451.79 299.29,-450.08 300.5,-456.97\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"151.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge4\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M242.7,-514.35C218.84,-504.16 188,-491 188,-491 188,-491 188,-482 188,-482 188,-482 255.7,-466.1 299.83,-455.73\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"300.74,-459.11 309.67,-453.42 299.14,-452.3 300.74,-459.11\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"204\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">465/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge5\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M256.82,-508.91C249.96,-500.03 243,-491 243,-491 243,-491 243,-482 243,-482 243,-482 273.97,-470.47 300.36,-460.64\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"301.66,-463.89 309.81,-457.12 299.22,-457.33 301.66,-463.89\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"259\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">587/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge6\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M280.91,-508.86C288.77,-496.51 298,-482 298,-482 298,-482 303.42,-477.13 310.23,-471.03\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"312.85,-473.37 317.96,-464.09 308.18,-468.16 312.85,-473.37\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"314\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">110/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge7\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.25,-517.71C331.26,-507.36 385,-491 385,-491 385,-491 385,-482 385,-482 385,-482 377.6,-476.6 368.6,-470.04\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"370.58,-467.15 360.44,-464.09 366.46,-472.81 370.58,-467.15\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"401\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">995/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge8\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.34,-519.66C340.86,-509.58 421,-491 421,-491 421,-491 421,-482 421,-482 421,-482 396.19,-471.66 373.54,-462.22\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.62,-458.88 364.04,-458.27 371.93,-465.34 374.62,-458.88\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"437\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">143/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge9\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.19,-510.14C313.07,-500.88 330,-491 330,-491 330,-491 331.24,-483.18 332.68,-474.13\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"336.16,-474.56 334.27,-464.14 329.25,-473.46 336.16,-474.56\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"348\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">993/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- internet->front -->\n",
|
||||
"<g id=\"edge10\" class=\"edge\">\n",
|
||||
"<title>internet->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M297.19,-520.91C349.06,-511.2 457,-491 457,-491 457,-491 457,-482 457,-482 457,-482 409.48,-468.14 374.25,-457.86\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.81,-454.38 364.23,-454.94 372.85,-461.1 374.81,-454.38\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"475.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">4190/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->front -->\n",
|
||||
"<g id=\"edge11\" class=\"edge\">\n",
|
||||
"<title>front->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.24,-449.75C374.02,-449.83 382,-448.58 382,-446 382,-444.43 379.04,-443.35 374.51,-442.77\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.4,-439.26 364.24,-442.25 374.05,-446.25 374.4,-439.26\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"400.5\" y=\"-444.1\" font-family=\"Times,serif\" font-size=\"8.00\">8008/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->front -->\n",
|
||||
"<g id=\"edge12\" class=\"edge\">\n",
|
||||
"<title>front->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.06,-452.31C389.18,-455.1 419,-452.99 419,-446 419,-439.94 396.57,-437.55 374.23,-438.84\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"373.73,-435.37 364.06,-439.69 374.31,-442.34 373.73,-435.37\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"437.5\" y=\"-444.1\" font-family=\"Times,serif\" font-size=\"8.00\">8000/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- admin -->\n",
|
||||
"<g id=\"node3\" class=\"node\">\n",
|
||||
"<title>admin</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"556,-302 502,-302 502,-266 556,-266 556,-302\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"529\" y=\"-282.1\" font-family=\"arial\" font-size=\"8.00\">Admin</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->admin -->\n",
|
||||
"<g id=\"edge13\" class=\"edge\">\n",
|
||||
"<title>front->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.05,-434.12C389.59,-423.84 424,-410 424,-410 424,-410 465,-347 465,-347 465,-347 485.85,-326.8 503.75,-309.46\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"506.43,-311.74 511.17,-302.27 501.55,-306.71 506.43,-311.74\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"483.5\" y=\"-363.1\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp -->\n",
|
||||
"<g id=\"node4\" class=\"node\">\n",
|
||||
"<title>smtp</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"565,-383 511,-383 511,-347 565,-347 565,-383\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"538\" y=\"-363.1\" font-family=\"arial\" font-size=\"8.00\">SMTP</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->smtp -->\n",
|
||||
"<g id=\"edge17\" class=\"edge\">\n",
|
||||
"<title>front->smtp</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.19,-439.53C413.12,-429.69 511,-410 511,-410 511,-410 516.33,-401.32 522.25,-391.67\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"525.24,-393.49 527.48,-383.14 519.27,-389.83 525.24,-393.49\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"529.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->smtp -->\n",
|
||||
"<g id=\"edge18\" class=\"edge\">\n",
|
||||
"<title>front->smtp</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.02,-440.5C420.74,-431.04 547,-410 547,-410 547,-410 547,-401 547,-401 547,-401 546.14,-397.65 544.95,-393.02\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"548.27,-391.9 542.39,-383.09 541.5,-393.65 548.27,-391.9\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"568.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">10025/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam -->\n",
|
||||
"<g id=\"node6\" class=\"node\">\n",
|
||||
"<title>antispam</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"632,-140 578,-140 578,-104 632,-104 632,-140\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"605\" y=\"-120.1\" font-family=\"arial\" font-size=\"8.00\">Antispam</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->antispam -->\n",
|
||||
"<g id=\"edge20\" class=\"edge\">\n",
|
||||
"<title>front->antispam</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M364.02,-441.32C430,-432.33 594,-410 594,-410 594,-410 649,-248 649,-248 649,-248 649,-239 649,-239 649,-239 620,-158 620,-158 620,-158 618.41,-154.3 616.27,-149.3\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"619.48,-147.9 612.32,-140.09 613.05,-150.66 619.48,-147.9\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"663.5\" y=\"-282.1\" font-family=\"Times,serif\" font-size=\"8.00\">11334/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap -->\n",
|
||||
"<g id=\"node8\" class=\"node\">\n",
|
||||
"<title>imap</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"411,-221 357,-221 357,-185 411,-185 411,-221\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"384\" y=\"-201.1\" font-family=\"arial\" font-size=\"8.00\">IMAP</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->imap -->\n",
|
||||
"<g id=\"edge14\" class=\"edge\">\n",
|
||||
"<title>front->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M338.92,-427.88C342.75,-393.65 351,-320 351,-320 351,-320 370,-239 370,-239 370,-239 371.41,-235.47 373.34,-230.66\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"376.7,-231.67 377.16,-221.09 370.2,-229.07 376.7,-231.67\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"369.5\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">4190/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->imap -->\n",
|
||||
"<g id=\"edge15\" class=\"edge\">\n",
|
||||
"<title>front->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M309.86,-441.8C236.54,-433.16 40,-410 40,-410 40,-410 0,-383 0,-383 0,-383 0,-347 0,-347 0,-347 286,-239 286,-239 286,-239 319.57,-227.01 347.42,-217.07\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"348.72,-220.32 356.96,-213.66 346.37,-213.72 348.72,-220.32\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"87\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">143/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->imap -->\n",
|
||||
"<g id=\"edge16\" class=\"edge\">\n",
|
||||
"<title>front->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M346.28,-427.91C351.1,-419.03 356,-410 356,-410 356,-410 388,-329 388,-329 388,-329 388,-320 388,-320 388,-320 386.13,-265.76 384.95,-231.46\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"388.43,-230.99 384.59,-221.12 381.44,-231.23 388.43,-230.99\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"404\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">110/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webdav -->\n",
|
||||
"<g id=\"node9\" class=\"node\">\n",
|
||||
"<title>webdav</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"63,-383 9,-383 9,-347 63,-347 63,-383\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"36\" y=\"-363.1\" font-family=\"arial\" font-size=\"8.00\">WebDAV</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->webdav -->\n",
|
||||
"<g id=\"edge21\" class=\"edge\">\n",
|
||||
"<title>front->webdav</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M309.79,-441.74C237.3,-433.05 45,-410 45,-410 45,-410 43.4,-402.18 41.55,-393.13\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"44.94,-392.23 39.51,-383.14 38.08,-393.64 44.94,-392.23\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"63.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">5232/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail -->\n",
|
||||
"<g id=\"node10\" class=\"node\">\n",
|
||||
"<title>webmail</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"211,-383 157,-383 157,-347 211,-347 211,-383\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"184\" y=\"-363.1\" font-family=\"arial\" font-size=\"8.00\">Webmail</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- front->webmail -->\n",
|
||||
"<g id=\"edge19\" class=\"edge\">\n",
|
||||
"<title>front->webmail</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M310,-437.06C274.89,-426.73 218,-410 218,-410 218,-410 211.02,-400.97 203.39,-391.09\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"206.13,-388.91 197.24,-383.14 200.59,-393.19 206.13,-388.91\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"231.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">80/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- redis -->\n",
|
||||
"<g id=\"node5\" class=\"node\">\n",
|
||||
"<title>redis</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"537,-59 483,-59 483,-23 537,-23 537,-59\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"510\" y=\"-39.1\" font-family=\"arial\" font-size=\"8.00\">Redis</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- admin->redis -->\n",
|
||||
"<g id=\"edge32\" class=\"edge\">\n",
|
||||
"<title>admin->redis</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M522.36,-265.88C509.84,-233.62 484,-167 484,-167 484,-167 484,-158 484,-158 484,-158 496.26,-103.29 503.95,-69.01\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"507.39,-69.64 506.16,-59.12 500.56,-68.11 507.39,-69.64\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"502.5\" y=\"-160.6\" font-family=\"Times,serif\" font-size=\"8.00\">6379/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- admin->imap -->\n",
|
||||
"<g id=\"edge33\" class=\"edge\">\n",
|
||||
"<title>admin->imap</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M501.75,-274.71C467.74,-264.36 414,-248 414,-248 414,-248 408.08,-239.32 401.5,-229.67\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"404.21,-227.43 395.68,-221.14 398.43,-231.37 404.21,-227.43\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"432.5\" y=\"-241.6\" font-family=\"Times,serif\" font-size=\"8.00\">2525/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp->front -->\n",
|
||||
"<g id=\"edge23\" class=\"edge\">\n",
|
||||
"<title>smtp->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M510.95,-379.68C485.41,-392.6 451,-410 451,-410 451,-410 407.25,-423.43 373.95,-433.65\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"372.55,-430.42 364.02,-436.71 374.6,-437.12 372.55,-430.42\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"485.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">2525/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp->admin -->\n",
|
||||
"<g id=\"edge22\" class=\"edge\">\n",
|
||||
"<title>smtp->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M536.05,-346.86C534.89,-336.71 533.4,-323.63 532.09,-312.12\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"535.56,-311.65 530.95,-302.11 528.6,-312.44 535.56,-311.65\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"551.5\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- smtp->antispam -->\n",
|
||||
"<g id=\"edge24\" class=\"edge\">\n",
|
||||
"<title>smtp->antispam</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M555.58,-346.91C564.72,-338.03 574,-329 574,-329 574,-329 608,-221 608,-221 608,-221 608,-185 608,-185 608,-185 607.12,-166.91 606.32,-150.27\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"609.81,-150.09 605.84,-140.27 602.82,-150.43 609.81,-150.09\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"623.5\" y=\"-241.6\" font-family=\"Times,serif\" font-size=\"8.00\">11332/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->admin -->\n",
|
||||
"<g id=\"edge35\" class=\"edge\">\n",
|
||||
"<title>antispam->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M597.99,-140.14C592.94,-152.49 587,-167 587,-167 587,-167 559.17,-222.66 542.07,-256.87\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"538.9,-255.37 537.56,-265.88 545.16,-258.5 538.9,-255.37\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"590.5\" y=\"-201.1\" font-family=\"Times,serif\" font-size=\"8.00\">80/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->redis -->\n",
|
||||
"<g id=\"edge34\" class=\"edge\">\n",
|
||||
"<title>antispam->redis</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M583.03,-103.91C571.6,-95.03 560,-86 560,-86 560,-86 548.91,-76.24 537.17,-65.91\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"539.29,-63.12 529.47,-59.14 534.67,-68.37 539.29,-63.12\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"578.5\" y=\"-79.6\" font-family=\"Times,serif\" font-size=\"8.00\">6379/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antivirus -->\n",
|
||||
"<g id=\"node7\" class=\"node\">\n",
|
||||
"<title>antivirus</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"698,-59 644,-59 644,-23 698,-23 698,-59\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"671\" y=\"-39.1\" font-family=\"arial\" font-size=\"8.00\">Anti-Virus</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->antivirus -->\n",
|
||||
"<g id=\"edge37\" class=\"edge\">\n",
|
||||
"<title>antispam->antivirus</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M626.48,-103.91C637.65,-95.03 649,-86 649,-86 649,-86 653.17,-77.67 657.87,-68.26\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"661.09,-69.65 662.43,-59.14 654.83,-66.52 661.09,-69.65\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"671.5\" y=\"-79.6\" font-family=\"Times,serif\" font-size=\"8.00\">3310/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- oletools -->\n",
|
||||
"<g id=\"node12\" class=\"node\">\n",
|
||||
"<title>oletools</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"626,-59 572,-59 572,-23 626,-23 626,-59\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"599\" y=\"-39.1\" font-family=\"arial\" font-size=\"8.00\">Oletools</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- antispam->oletools -->\n",
|
||||
"<g id=\"edge36\" class=\"edge\">\n",
|
||||
"<title>antispam->oletools</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M603.7,-103.86C602.93,-93.71 601.93,-80.63 601.06,-69.12\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"604.55,-68.81 600.3,-59.11 597.57,-69.34 604.55,-68.81\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"623.5\" y=\"-79.6\" font-family=\"Times,serif\" font-size=\"8.00\">11343/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->front -->\n",
|
||||
"<g id=\"edge27\" class=\"edge\">\n",
|
||||
"<title>imap->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M374.23,-221.09C369.16,-229.97 364,-239 364,-239 364,-239 320,-320 320,-320 320,-320 320,-329 320,-329 320,-329 328.02,-383.71 333.04,-417.99\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"329.58,-418.5 334.49,-427.88 336.5,-417.48 329.58,-418.5\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"333.5\" y=\"-322.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->admin -->\n",
|
||||
"<g id=\"edge25\" class=\"edge\">\n",
|
||||
"<title>imap->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M411.25,-217.43C431.27,-227.3 455,-239 455,-239 455,-239 474.19,-250.41 493.02,-261.61\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"491.48,-264.76 501.87,-266.87 495.06,-258.75 491.48,-264.76\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"487.5\" y=\"-241.6\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->antispam -->\n",
|
||||
"<g id=\"edge26\" class=\"edge\">\n",
|
||||
"<title>imap->antispam</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M411.19,-195.25C452.16,-185.08 525,-167 525,-167 525,-167 547.74,-154.49 568.98,-142.81\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"570.84,-145.78 577.92,-137.9 567.47,-139.65 570.84,-145.78\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"561.5\" y=\"-160.6\" font-family=\"Times,serif\" font-size=\"8.00\">11334/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fts_attachments -->\n",
|
||||
"<g id=\"node13\" class=\"node\">\n",
|
||||
"<title>fts_attachments</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"411,-140 357,-140 357,-104 411,-104 411,-140\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"384\" y=\"-120.1\" font-family=\"arial\" font-size=\"8.00\">Tika</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- imap->fts_attachments -->\n",
|
||||
"<g id=\"edge28\" class=\"edge\">\n",
|
||||
"<title>imap->fts_attachments</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M384,-184.86C384,-174.71 384,-161.63 384,-150.12\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"387.5,-150.11 384,-140.11 380.5,-150.11 387.5,-150.11\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"402.5\" y=\"-160.6\" font-family=\"Times,serif\" font-size=\"8.00\">9998/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail->front -->\n",
|
||||
"<g id=\"edge29\" class=\"edge\">\n",
|
||||
"<title>webmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M211.04,-380.16C235.6,-393.03 268,-410 268,-410 268,-410 283.98,-418.1 300.56,-426.51\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"299.23,-429.76 309.73,-431.17 302.39,-423.52 299.23,-429.76\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"289.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">14190/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail->front -->\n",
|
||||
"<g id=\"edge30\" class=\"edge\">\n",
|
||||
"<title>webmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M156.96,-375.66C127.95,-386.02 86,-401 86,-401 86,-401 86,-410 86,-410 86,-410 229.5,-430.01 299.49,-439.77\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"299.27,-443.27 309.66,-441.19 300.24,-436.34 299.27,-443.27\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"107.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">10025/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- webmail->front -->\n",
|
||||
"<g id=\"edge31\" class=\"edge\">\n",
|
||||
"<title>webmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M174.72,-383.09C169.9,-391.97 165,-401 165,-401 165,-401 165,-410 165,-410 165,-410 249.24,-427.14 299.81,-437.43\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"299.25,-440.89 309.75,-439.45 300.64,-434.03 299.25,-440.89\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"186.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">10143/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail -->\n",
|
||||
"<g id=\"node11\" class=\"node\">\n",
|
||||
"<title>fetchmail</title>\n",
|
||||
"<polygon fill=\"#d3edea\" stroke=\"#d3edea\" points=\"654,-545 600,-545 600,-509 654,-509 654,-545\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"627\" y=\"-525.1\" font-family=\"arial\" font-size=\"8.00\">Fetchmail</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail->front -->\n",
|
||||
"<g id=\"edge39\" class=\"edge\">\n",
|
||||
"<title>fetchmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M599.96,-516.78C562.13,-503.87 498,-482 498,-482 498,-482 421.83,-465.44 374.28,-455.1\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.83,-451.64 364.31,-452.94 373.34,-458.48 374.83,-451.64\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"535.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">25/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail->front -->\n",
|
||||
"<g id=\"edge40\" class=\"edge\">\n",
|
||||
"<title>fetchmail->front</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M599.87,-509.87C578.75,-497.31 553,-482 553,-482 553,-482 436.36,-463.1 374.51,-453.08\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"374.79,-449.58 364.36,-451.43 373.67,-456.49 374.79,-449.58\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"585.5\" y=\"-484.6\" font-family=\"Times,serif\" font-size=\"8.00\">2525/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"<!-- fetchmail->admin -->\n",
|
||||
"<g id=\"edge38\" class=\"edge\">\n",
|
||||
"<title>fetchmail->admin</title>\n",
|
||||
"<path fill=\"none\" stroke=\"black\" d=\"M622.57,-508.79C609.83,-459.28 574,-320 574,-320 574,-320 567.2,-314.71 558.88,-308.24\"/>\n",
|
||||
"<polygon fill=\"black\" stroke=\"black\" points=\"561.02,-305.47 550.97,-302.09 556.72,-310.99 561.02,-305.47\"/>\n",
|
||||
"<text text-anchor=\"middle\" x=\"614.5\" y=\"-403.6\" font-family=\"Times,serif\" font-size=\"8.00\">8080/tcp</text>\n",
|
||||
"</g>\n",
|
||||
"</g>\n",
|
||||
"</svg>\n"
|
||||
],
|
||||
"text/plain": [
|
||||
"<graphviz.sources.Source at 0x7f2c4e69e690>"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import graphviz\n",
|
||||
"\n",
|
||||
"a = \"\"\"\n",
|
||||
"digraph mailu {\n",
|
||||
" label = \"Mailu\";\n",
|
||||
" fontname = \"arial\";\n",
|
||||
" \n",
|
||||
" node [shape = box; fontname = \"arial\"; fontsize = 8; style = filled; color = \"#d3edea\";];\n",
|
||||
" splines = \"compound\";\n",
|
||||
" // node [shape = \"box\"; fontsize = \"10\";];\n",
|
||||
" edge [fontsize = \"8\";];\n",
|
||||
" \n",
|
||||
" # Components\n",
|
||||
" internet [label = \"Internet\";];\n",
|
||||
" front [label = \"Front\";];\n",
|
||||
" admin [label = \"Admin\";];\n",
|
||||
" smtp [label = \"SMTP\";];\n",
|
||||
" redis [label = \"Redis\";];\n",
|
||||
" antispam [label = \"Antispam\";];\n",
|
||||
" antivirus [label = \"Anti-Virus\";];\n",
|
||||
" imap [label = \"IMAP\";];\n",
|
||||
" webdav [label = \"WebDAV\";];\n",
|
||||
" webmail [label = \"Webmail\";];\n",
|
||||
" fetchmail [label = \"Fetchmail\";];\n",
|
||||
" oletools [label = \"Oletools\"];\n",
|
||||
" fts_attachments [label = \"Tika\"];\n",
|
||||
" \n",
|
||||
" # Front from internet\n",
|
||||
" internet -> front [label = \"80/tcp\";];\n",
|
||||
" internet -> front [label = \"443/tcp\";];\n",
|
||||
" internet -> front [label = \"25/tcp\";];\n",
|
||||
" internet -> front [label = \"465/tcp\";];\n",
|
||||
" internet -> front [label = \"587/tcp\";];\n",
|
||||
" internet -> front [label = \"110/tcp\";];\n",
|
||||
" internet -> front [label = \"995/tcp\";];\n",
|
||||
" internet -> front [label = \"143/tcp\";];\n",
|
||||
" internet -> front [label = \"993/tcp\";];\n",
|
||||
" internet -> front [label = \"4190/tcp\";];\n",
|
||||
" \n",
|
||||
" front -> front [label = \"8008/tcp\";];\n",
|
||||
" front -> front [label = \"8000/tcp\";];\n",
|
||||
" front -> admin [label = \"8080/tcp\";];\n",
|
||||
" front -> imap [label = \"4190/tcp\";];\n",
|
||||
" front -> imap [label = \"143/tcp\";];\n",
|
||||
" front -> imap [label = \"110/tcp\";];\n",
|
||||
" front -> smtp [label = \"25/tcp\";];\n",
|
||||
" front -> smtp [label = \"10025/tcp\";];\n",
|
||||
" front -> webmail [label = \"80/tcp\";];\n",
|
||||
" front -> antispam [label = \"11334/tcp\";];\n",
|
||||
" front -> webdav [label = \"5232/tcp\";];\n",
|
||||
" \n",
|
||||
" smtp -> admin [label = \"8080/tcp\";];\n",
|
||||
" smtp -> front [label = \"2525/tcp\";];\n",
|
||||
" smtp -> antispam [label = \"11332/tcp\";];\n",
|
||||
" \n",
|
||||
" imap -> admin [label = \"8080/tcp\";];\n",
|
||||
" imap -> antispam [label = \"11334/tcp\";];\n",
|
||||
" imap -> front [label = \"25/tcp\";];\n",
|
||||
" imap -> fts_attachments [label = \"9998/tcp\";];\n",
|
||||
" \n",
|
||||
" webmail -> front [label = \"14190/tcp\";];\n",
|
||||
" webmail -> front [label = \"10025/tcp\";];\n",
|
||||
" webmail -> front [label = \"10143/tcp\";];\n",
|
||||
" \n",
|
||||
" admin -> redis [label = \"6379/tcp\";];\n",
|
||||
" admin -> imap [label = \"2525/tcp\";];\n",
|
||||
" \n",
|
||||
" antispam -> redis [label = \"6379/tcp\";];\n",
|
||||
" antispam -> admin [label = \"80/tcp\";];\n",
|
||||
" antispam -> oletools [label = \"11343/tcp\";];\n",
|
||||
" antispam -> antivirus [label = \"3310/tcp\";];\n",
|
||||
" \n",
|
||||
" fetchmail -> admin [label = \"8080/tcp\"]\n",
|
||||
" fetchmail -> front [label = \"25/tcp\"]\n",
|
||||
" fetchmail -> front [label = \"2525/tcp\"]\n",
|
||||
" #\n",
|
||||
" # those don't need internet:\n",
|
||||
" # oletools\n",
|
||||
" # fts_attachments\n",
|
||||
" # redis\n",
|
||||
"}\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"dot = graphviz.Source(a)\n",
|
||||
"dot\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -342,9 +342,9 @@ A fair amount of work went in this release; In no particular order:
|
||||
- outbound SMTP connections from Mailu are now enjoying some protection against active attackers thanks to DANE and MTA-STS support. Specific policies can be configured for specific destinations thanks to ``tls_policy_maps`` and configuring your system to publish a policy has been documented in the FAQ.
|
||||
- outbound emails can now be rate-limited (to mitigate SPAM in case an account is taken over)
|
||||
- long term storage of passwords has been rethought to enable stronger protection against offline attackers (switch to iterated and salted SHA+bcrypt) while enabling much better performance (credential cache). Please encourage your users to use tokens where appropriate and keep in mind that existing hashes will be converted on first use to the new format.
|
||||
- session handling has been reworked from the grounds up: they have been switched from client side (cookies) to server-side, unified (SSO, expiry, lifetime) accross all web-facing applications and some mitigations against session fixation have been implemented.
|
||||
- rate limiting has seen many improvements: It is now deployed on all entry points (SMTP/IMAP/POP3/WEB/WEBMAIL) and configured to defeat both password bruteforces (thanks to a limit against total number of failed attempts against an account over a period) and password spraying (thanks to a limit for each client on the total number of non-existing accounts that can be queried). Exemption mechanisms have been put in place (device tokens, dynamic IP whitelists) to ensure that genuine clients and users won't be affected by default and the default configuration thought to fit most usecases.
|
||||
- if you use letsencrypt, Mailu is now configured to offer both RSA and ECC certificates to clients; It will OSCP stapple its replies where appropriate
|
||||
- session handling has been reworked from the grounds up: they have been switched from client side (cookies) to server-side, unified (SSO, expiry, lifetime) across all web-facing applications and some mitigations against session fixation have been implemented.
|
||||
- rate limiting has seen many improvements: It is now deployed on all entry points (SMTP/IMAP/POP3/WEB/WEBMAIL) and configured to defeat both password bruteforces (thanks to a limit against total number of failed attempts against an account over a period) and password spraying (thanks to a limit for each client on the total number of non-existing accounts that can be queried). Exemption mechanisms have been put in place (device tokens, dynamic IP whitelists) to ensure that genuine clients and users won't be affected by default and the default configuration thought to fit most use-cases.
|
||||
- if you use letsencrypt, Mailu is now configured to offer both RSA and ECC certificates to clients; It will OSCP staple its replies where appropriate
|
||||
|
||||
|
||||
Updated Admin interface
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
recommonmark==0.7.1
|
||||
Sphinx==5.2.0
|
||||
Sphinx==7.2.6
|
||||
sphinx-autobuild==2021.3.14
|
||||
sphinx-rtd-theme==1.0.0
|
||||
docutils==0.16
|
||||
sphinx-rtd-theme==1.3.0
|
||||
docutils==0.18.1
|
||||
|
||||
@@ -4,7 +4,7 @@ Using an external reverse proxy
|
||||
One of Mailu's use cases is as part of a larger services platform, where maybe
|
||||
other Web services are available on other FQDNs served from the same IP address.
|
||||
|
||||
In such a configuration, one would usually run a frontend reverse proxy to serve all
|
||||
In such a configuration, one would usually run a front-end reverse proxy to serve all
|
||||
Web contents based on criteria like the requested hostname (virtual hosts).
|
||||
|
||||
.. _traefik_proxy:
|
||||
@@ -134,4 +134,4 @@ in mailu.env:
|
||||
TLS_FLAVOR=mail-letsencrypt
|
||||
WEBROOT_REDIRECT=/sso/login
|
||||
|
||||
Using the above configuration, Traefik will proxy all the traffic related to Mailu's FQDNs without requiring dupplicate certificates.
|
||||
Using the above configuration, Traefik will proxy all the traffic related to Mailu's FQDNs without requiring duplicate certificates.
|
||||
|
||||
@@ -254,7 +254,7 @@ The menu item Antispam opens the Rspamd webgui. For more information how spam fi
|
||||
The spam filtering page also contains a section that describes how to create a local blacklist for blocking email messages from specific domains.
|
||||
The Rspamd webgui offers basic functions for setting metric actions, scores, viewing statistics and learning.
|
||||
|
||||
The following settings are not persisent and are *lost* when the Antispam container is recreated or restarted:
|
||||
The following settings are not persistent and are *lost* when the Antispam container is recreated or restarted:
|
||||
|
||||
* On the configuration tab, any changes to config files that do not reside in /var/lib or /etc/rspamd/override.d. The last location is mapped to the Mailu overrides folder.
|
||||
|
||||
@@ -282,7 +282,7 @@ On the `Mail domains` page all the domains served by Mailu are configured. Via t
|
||||
Details
|
||||
```````
|
||||
|
||||
This page is also accessible for domain managers. On the details page all DNS settings are displayed for configuring your DNS server. It contains information on what to configure as MX record and SPF record. On this page it is also possible to (re-)generate the keys for DKIM and DMARC. The option for generating keys for DKIM and DMARC is only available for global administrators. After generating the keys for DKIM and DMARC, this page will also show the DNS records for configuring the DKIM/DMARC records on the DNS server.
|
||||
This page is also accessible for domain managers. On the details page all DNS settings are displayed for configuring your DNS server. It contains information on what to configure as MX record and SPF record. On this page it is also possible to (re-)generate the keys for DKIM and DMARC. The option for generating keys for DKIM and DMARC is only available for global administrators. After generating the keys for DKIM and DMARC, this page will also show the DNS records for configuring the DKIM/DMARC records on the DNS server. You can also download a zonefile for easy upload to your nameserver.
|
||||
|
||||
|
||||
Edit
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# syntax=docker/dockerfile-upstream:1.4.3
|
||||
|
||||
# clamav image
|
||||
FROM base
|
||||
|
||||
ARG VERSION=local
|
||||
LABEL version=$VERSION
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; apk add --no-cache clamav clamav-libunrar rsyslog wget
|
||||
|
||||
COPY conf/ /etc/clamav/
|
||||
COPY start.py /
|
||||
|
||||
RUN echo $VERSION >/version
|
||||
|
||||
#EXPOSE 3310/tcp
|
||||
HEALTHCHECK CMD kill -0 `cat /run/clamd.pid` && kill -0 `cat /run/freshclam.pid`
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
CMD /start.py
|
||||
@@ -1,12 +0,0 @@
|
||||
Mailu ClamAV container
|
||||
======================
|
||||
|
||||
ClamAV is an open source antivirus engine for detecting trojans, viruses,
|
||||
malware & other malicious threats.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Report issues](https://github.com/Mailu/Mailu/issues) and
|
||||
[send Pull Requests](https://github.com/Mailu/Mailu/pulls)
|
||||
in the [main Mailu repository](https://github.com/Mailu/Mailu)
|
||||
@@ -1,56 +0,0 @@
|
||||
###############
|
||||
# General
|
||||
###############
|
||||
|
||||
DatabaseDirectory /data
|
||||
TemporaryDirectory /tmp
|
||||
LogTime yes
|
||||
PidFile /run/clamd.pid
|
||||
LocalSocket /tmp/clamd.sock
|
||||
TCPSocket 3310
|
||||
Foreground yes
|
||||
|
||||
###############
|
||||
# Results
|
||||
###############
|
||||
|
||||
DetectPUA yes
|
||||
ExcludePUA NetTool
|
||||
ExcludePUA PWTool
|
||||
HeuristicAlerts yes
|
||||
Bytecode yes
|
||||
|
||||
###############
|
||||
# Scan
|
||||
###############
|
||||
|
||||
ScanPE yes
|
||||
DisableCertCheck yes
|
||||
ScanELF yes
|
||||
AlertBrokenExecutables yes
|
||||
ScanOLE2 yes
|
||||
ScanPDF yes
|
||||
ScanSWF yes
|
||||
ScanMail yes
|
||||
PhishingSignatures yes
|
||||
PhishingScanURLs yes
|
||||
ScanHTML yes
|
||||
ScanArchive yes
|
||||
|
||||
###############
|
||||
# Scan
|
||||
###############
|
||||
|
||||
MaxScanSize 150M
|
||||
MaxFileSize 30M
|
||||
MaxRecursion 10
|
||||
MaxFiles 15000
|
||||
MaxEmbeddedPE 10M
|
||||
MaxHTMLNormalize 10M
|
||||
MaxHTMLNoTags 2M
|
||||
MaxScriptNormalize 5M
|
||||
MaxZipTypeRcg 1M
|
||||
MaxPartitions 128
|
||||
MaxIconsPE 200
|
||||
PCREMatchLimit 10000
|
||||
PCRERecMatchLimit 10000
|
||||
@@ -1,18 +0,0 @@
|
||||
###############
|
||||
# General
|
||||
###############
|
||||
|
||||
DatabaseDirectory /data
|
||||
UpdateLogFile /dev/stdout
|
||||
LogTime yes
|
||||
PidFile /run/freshclam.pid
|
||||
DatabaseOwner root
|
||||
|
||||
###############
|
||||
# Updates
|
||||
###############
|
||||
|
||||
DatabaseMirror database.clamav.net
|
||||
ScriptedUpdates yes
|
||||
NotifyClamd /etc/clamav/clamd.conf
|
||||
Bytecode yes
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import logging as logger
|
||||
import sys
|
||||
from socrate import system
|
||||
|
||||
system.set_env(log_filters=r'SelfCheck: Database status OK\.$')
|
||||
|
||||
# Bootstrap the database if clamav is running for the first time
|
||||
if not os.path.isfile("/data/main.cvd"):
|
||||
logger.info("Starting primary virus DB download")
|
||||
os.system("freshclam")
|
||||
|
||||
# Run the update daemon
|
||||
logger.info("Starting the update daemon")
|
||||
os.system("freshclam -d -c 6")
|
||||
|
||||
# Run clamav
|
||||
logger.info("Starting clamav")
|
||||
os.system("clamd")
|
||||
@@ -64,7 +64,7 @@ def run(debug):
|
||||
username=escape_rc_string(fetch["username"]),
|
||||
password=escape_rc_string(fetch["password"]),
|
||||
options=options,
|
||||
folders=folders,
|
||||
folders='' if fetch['protocol'] == 'pop3' else folders,
|
||||
lmtp='' if fetch['scan'] else 'lmtp',
|
||||
)
|
||||
if debug:
|
||||
|
||||
@@ -58,6 +58,10 @@ services:
|
||||
resolver:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-{{ version }}}
|
||||
env_file: {{ env }}
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-resolver
|
||||
restart: always
|
||||
networks:
|
||||
default:
|
||||
@@ -98,8 +102,16 @@ services:
|
||||
volumes:
|
||||
- "{{ root }}/mail:/mail"
|
||||
- "{{ root }}/overrides/dovecot:/overrides:ro"
|
||||
networks:
|
||||
- default
|
||||
{% if tika_enabled %}
|
||||
- fts_attachments
|
||||
{% endif %}
|
||||
depends_on:
|
||||
- front
|
||||
{% if tika_enabled %}
|
||||
- fts_attachments
|
||||
{% endif %}
|
||||
{% if resolver_enabled %}
|
||||
- resolver
|
||||
dns:
|
||||
@@ -129,9 +141,13 @@ services:
|
||||
oletools:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}oletools:${MAILU_VERSION:-{{ version }}}
|
||||
hostname: oletools
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-oletools
|
||||
restart: always
|
||||
networks:
|
||||
- noinet
|
||||
- oletools
|
||||
depends_on:
|
||||
{% if resolver_enabled %}
|
||||
- resolver
|
||||
@@ -140,6 +156,31 @@ services:
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if tika_enabled %}
|
||||
fts_attachments:
|
||||
image: ghcr.io/paperless-ngx/tika:2.9.0-full
|
||||
hostname: tika
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-tika
|
||||
restart: always
|
||||
networks:
|
||||
- fts_attachments
|
||||
depends_on:
|
||||
{% if resolver_enabled %}
|
||||
- resolver
|
||||
dns:
|
||||
- {{ dns }}
|
||||
{% endif %}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -nv -t1 -O /dev/null http://127.0.0.1:9998/tika || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
{% endif %}
|
||||
|
||||
antispam:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-{{ version }}}
|
||||
hostname: antispam
|
||||
@@ -149,10 +190,13 @@ services:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-antispam
|
||||
{% if oletools_enabled %}
|
||||
networks:
|
||||
- default
|
||||
- noinet
|
||||
{% if oletools_enabled %}
|
||||
- oletools
|
||||
{% endif %}
|
||||
{% if antivirus_enabled %}
|
||||
- clamav
|
||||
{% endif %}
|
||||
volumes:
|
||||
- "{{ root }}/filter:/var/lib/rspamd"
|
||||
@@ -175,23 +219,32 @@ services:
|
||||
# Optional services
|
||||
{% if antivirus_enabled %}
|
||||
antivirus:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-{{ version }}}
|
||||
image: clamav/clamav-debian:1.2.0-6
|
||||
restart: always
|
||||
env_file: {{ env }}
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-antivirus
|
||||
networks:
|
||||
- clamav
|
||||
volumes:
|
||||
- "{{ root }}/filter:/data"
|
||||
{% if resolver_enabled %}
|
||||
depends_on:
|
||||
- resolver
|
||||
dns:
|
||||
- {{ dns }}
|
||||
{% endif %}
|
||||
- "{{ root }}/filter/clamav:/var/lib/clamav"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kill -0 `cat /tmp/clamd.pid` && kill -0 `cat /tmp/freshclam.pid`"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
{% endif %}
|
||||
|
||||
{% if webdav_enabled %}
|
||||
webdav:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-{{ version }}}
|
||||
restart: always
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-webdav
|
||||
volumes:
|
||||
- "{{ root }}/dav:/data"
|
||||
networks:
|
||||
@@ -203,6 +256,10 @@ services:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-{{ version }}}
|
||||
restart: always
|
||||
env_file: {{ env }}
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-fetchmail
|
||||
volumes:
|
||||
- "{{ root }}/data/fetchmail:/data"
|
||||
depends_on:
|
||||
@@ -222,6 +279,10 @@ services:
|
||||
image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}webmail:${MAILU_VERSION:-{{ version }}}
|
||||
restart: always
|
||||
env_file: {{ env }}
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-webmail
|
||||
volumes:
|
||||
- "{{ root }}/webmail:/data"
|
||||
- "{{ root }}/overrides/{{ webmail_type }}:/overrides:ro"
|
||||
@@ -252,8 +313,17 @@ networks:
|
||||
webmail:
|
||||
driver: bridge
|
||||
{% endif %}
|
||||
{% if antivirus_enabled %}
|
||||
clamav:
|
||||
driver: bridge
|
||||
{% endif %}
|
||||
{% if oletools_enabled %}
|
||||
noinet:
|
||||
oletools:
|
||||
driver: bridge
|
||||
internal: true
|
||||
{% endif %}
|
||||
{% if tika_enabled %}
|
||||
fts_attachments:
|
||||
driver: bridge
|
||||
internal: true
|
||||
{% endif %}
|
||||
|
||||
@@ -40,7 +40,11 @@ AUTH_RATELIMIT_USER={{ auth_ratelimit_user }}/day
|
||||
{% endif %}
|
||||
|
||||
# Opt-out of statistics, replace with "True" to opt out
|
||||
DISABLE_STATISTICS={{ disable_statistics or 'False' }}
|
||||
{% if statistics_enabled %}
|
||||
DISABLE_STATISTICS=False
|
||||
{% else %}
|
||||
DISABLE_STATISTICS=True
|
||||
{% endif %}
|
||||
|
||||
###################################
|
||||
# Optional features
|
||||
@@ -49,19 +53,19 @@ DISABLE_STATISTICS={{ disable_statistics or 'False' }}
|
||||
# Expose the admin interface (value: true, false)
|
||||
ADMIN={{ admin_enabled or 'false' }}
|
||||
|
||||
# Choose which webmail to run if any (values: roundcube, snappymail, none)
|
||||
# Choose which webmail to run if any (values: roundcube, snappymail, none). To enable this feature, recreate the docker-compose.yml file via setup.
|
||||
WEBMAIL={{ webmail_type }}
|
||||
|
||||
# Expose the API interface (value: true, false)
|
||||
API={{ api_enabled or 'false' }}
|
||||
|
||||
# Dav server implementation (value: radicale, none)
|
||||
# Dav server implementation (value: radicale, none). To enable this feature, recreate the docker-compose.yml file via setup.
|
||||
WEBDAV={{ webdav_enabled or 'none' }}
|
||||
|
||||
# Antivirus solution (value: clamav, none)
|
||||
# Antivirus solution (value: clamav, none). To enable this feature, recreate the docker-compose.yml file via setup.
|
||||
ANTIVIRUS={{ antivirus_enabled or 'none' }}
|
||||
|
||||
# Scan Macros solution (value: true, false)
|
||||
# Scan Macros solution (value: true, false). To enable this feature, recreate the docker-compose.yml file via setup.
|
||||
SCAN_MACROS={{ oletools_enabled or 'false' }}
|
||||
|
||||
###################################
|
||||
@@ -110,32 +114,32 @@ COMPRESSION={{ compression }}
|
||||
# change compression-level, default: 6 (value: 1-9)
|
||||
COMPRESSION_LEVEL={{ compression_level }}
|
||||
|
||||
# IMAP full-text search is enabled by default. Set the following variable to off in order to disable the feature.
|
||||
# FULL_TEXT_SEARCH=off
|
||||
# IMAP full-text search is enabled by default.
|
||||
# Set the following variable to off in order to disable the feature
|
||||
# or a comma separated list of language codes to support
|
||||
FULL_TEXT_SEARCH=en
|
||||
|
||||
###################################
|
||||
# Web settings
|
||||
###################################
|
||||
|
||||
# Path to redirect / to
|
||||
{% if webmail_type != 'none' and webmail_path == '' %}
|
||||
WEBROOT_REDIRECT=/
|
||||
{% if webmail_type != 'none' %}
|
||||
WEBROOT_REDIRECT=/webmail
|
||||
{% elif admin_enabled %}
|
||||
WEBROOT_REDIRECT=/admin
|
||||
{% else %}
|
||||
WEBROOT_REDIRECT={{ webmail_path }}
|
||||
WEBROOT_REDIRECT=
|
||||
{% endif %}
|
||||
|
||||
# Path to the admin interface if enabled
|
||||
WEB_ADMIN={{ admin_path }}
|
||||
WEB_ADMIN=/admin
|
||||
|
||||
# Path to the webmail if enabled
|
||||
{% if webmail_type != 'none' and webmail_path == '' %}
|
||||
WEB_WEBMAIL=/
|
||||
{% else %}
|
||||
WEB_WEBMAIL={{ webmail_path }}
|
||||
{% endif %}
|
||||
WEB_WEBMAIL=/webmail
|
||||
|
||||
# Path to the API interface if enabled
|
||||
WEB_API={{ api_path }}
|
||||
WEB_API=/api
|
||||
|
||||
# Website name
|
||||
SITENAME={{ site_name }}
|
||||
@@ -186,3 +190,5 @@ DEFAULT_SPAM_THRESHOLD=80
|
||||
# This is a mandatory setting for using the RESTful API.
|
||||
API_TOKEN={{ api_token }}
|
||||
|
||||
# Whether tika should be enabled (scan/OCR email attachements). To enable this feature, recreate the docker-compose.yml file via setup.
|
||||
FULL_TEXT_SEARCH_ATTACHMENTS={{ tika_enabled }}
|
||||
|
||||
@@ -10,12 +10,16 @@ import random
|
||||
import ipaddress
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
import secrets
|
||||
from flask_bootstrap import StaticCDN
|
||||
|
||||
version = os.getenv("this_version", "master")
|
||||
static_url_path = "/" + version + "/static"
|
||||
app = flask.Flask(__name__, static_url_path=static_url_path)
|
||||
app.secret_key = secrets.token_hex(16)
|
||||
flask_bootstrap.Bootstrap(app)
|
||||
# Load our jQuery. Do not use jQuery 1.
|
||||
app.extensions['bootstrap']['cdns']['jquery'] = StaticCDN()
|
||||
db = redis.StrictRedis(host='redis', port=6379, db=0)
|
||||
|
||||
|
||||
@@ -90,12 +94,47 @@ def build_app(path):
|
||||
def submit():
|
||||
data = flask.request.form.copy()
|
||||
data['uid'] = str(uuid.uuid4())
|
||||
valid = True
|
||||
try:
|
||||
ipaddress.IPv4Address(data['bind4'])
|
||||
except:
|
||||
flask.flash('Configured IPv4 address is invalid', 'error')
|
||||
valid = False
|
||||
try:
|
||||
ipaddress.IPv6Address(data['bind6'])
|
||||
except:
|
||||
flask.flash('Configured IPv6 address is invalid', 'error')
|
||||
valid = False
|
||||
try:
|
||||
ipaddress.IPv4Network(data['subnet'])
|
||||
except:
|
||||
flask.flash('Configured subnet(IPv4) is invalid', 'error')
|
||||
valid = False
|
||||
try:
|
||||
ipaddress.IPv6Network(data['subnet6'])
|
||||
except:
|
||||
flask.flash('Configured subnet(IPv6) is invalid', 'error')
|
||||
valid = False
|
||||
try:
|
||||
data['dns'] = str(ipaddress.IPv4Network(data['subnet'], strict=False)[-2])
|
||||
except ValueError as err:
|
||||
return "Error while generating files: " + str(err)
|
||||
db.set(data['uid'], json.dumps(data))
|
||||
return flask.redirect(flask.url_for('.setup', uid=data['uid']))
|
||||
flask.flash('Invalid configuration: ' + str(err))
|
||||
valid = False
|
||||
if 'api_enabled' in data:
|
||||
if (data['api_enabled'] == 'true'):
|
||||
if data['api_token'] == '':
|
||||
flask.flash('API token cannot be empty when API is enabled', 'error')
|
||||
valid = False
|
||||
if valid:
|
||||
db.set(data['uid'], json.dumps(data))
|
||||
return flask.redirect(flask.url_for('.setup', uid=data['uid']))
|
||||
else:
|
||||
return flask.render_template(
|
||||
'wizard.html',
|
||||
flavor="compose",
|
||||
steps=sorted(os.listdir(os.path.join(path, "templates", "steps", "compose"))),
|
||||
subnet6=random_ipv6_subnet()
|
||||
)
|
||||
|
||||
@prefix_bp.route("/setup/<uid>", methods=["GET"])
|
||||
@root_bp.route("/setup/<uid>", methods=["GET"])
|
||||
|
||||
2
setup/static/jquery.min.js
vendored
Normal file
2
setup/static/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -2,51 +2,17 @@
|
||||
var token = $("#api_token").val();
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($("#webmail").val() == 'none') {
|
||||
$("#webmail_path").hide();
|
||||
$("#webmail_path").val("");
|
||||
} else {
|
||||
$("#webmail_path").show();
|
||||
$("#webmail_path").val("/webmail");
|
||||
}
|
||||
$("#webmail").click(function() {
|
||||
if (this.value == 'none') {
|
||||
$("#webmail_path").hide();
|
||||
$("#webmail_path").val("");
|
||||
} else {
|
||||
$("#webmail_path").show();
|
||||
$("#webmail_path").val("/webmail");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($('#admin').prop('checked')) {
|
||||
$("#admin_path").show();
|
||||
$("#admin_path").val("/admin");
|
||||
}
|
||||
$("#admin").change(function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$("#admin_path").show();
|
||||
$("#admin_path").val("/admin");
|
||||
} else {
|
||||
$("#admin_path").hide();
|
||||
$("#admin_path").val("");
|
||||
}
|
||||
});
|
||||
$("#no_java_script").hide();
|
||||
$("#container").show();
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($('#api_enabled').prop('checked')) {
|
||||
$("#api_path").show();
|
||||
$("#api_path").val("/api")
|
||||
$("#api_token").show();
|
||||
$("#api_token").prop('required',true);
|
||||
$("#api_token").val(token);
|
||||
$("#api_token_label").show();
|
||||
} else {
|
||||
$("#api_path").hide();
|
||||
$("#api_path").val("")
|
||||
$("#api_token").hide();
|
||||
$("#api_token").prop('required',false);
|
||||
$("#api_token").val("");
|
||||
@@ -54,15 +20,11 @@ $(document).ready(function() {
|
||||
}
|
||||
$("#api_enabled").change(function() {
|
||||
if ($(this).is(":checked")) {
|
||||
$("#api_path").show();
|
||||
$("#api_path").val("/api");
|
||||
$("#api_token").show();
|
||||
$("#api_token").prop('required',true);
|
||||
$("#api_token").val(token)
|
||||
$("#api_token_label").show();
|
||||
} else {
|
||||
$("#api_path").hide();
|
||||
$("#api_path").val("")
|
||||
$("#api_token").hide();
|
||||
$("#api_token").prop('required',false);
|
||||
$("#api_token").val("");
|
||||
@@ -71,45 +33,6 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($("#database").val() == 'sqlite') {
|
||||
$("#external_db").hide();
|
||||
} else {
|
||||
$("#external_db").show();
|
||||
}
|
||||
|
||||
$("#webmail").click(function() {
|
||||
if (this.value == 'roundcube') {
|
||||
$("#db_flavor_rc_sel").show();
|
||||
} else {
|
||||
$("#db_flavor_rc_sel").hide();
|
||||
$("#roundcube_db_user,#roundcube_db_pw,#roundcube_db_url,#roundcube_db_name").prop('required',false);
|
||||
}
|
||||
});
|
||||
|
||||
$("#database").click(function() {
|
||||
if (this.value == 'sqlite') {
|
||||
$("#external_db").hide();
|
||||
$("#db_user,#db_pw,#db_url,#db_name").prop('required',false);
|
||||
$("#roundcube_db_user,#roundcube_db_pw,#roundcube_db_url,#roundcube_db_name").prop('required',false);
|
||||
} else {
|
||||
$("#external_db").show();
|
||||
$("#db_user,#db_pw,#db_url,#db_name").prop('required',true);
|
||||
}
|
||||
});
|
||||
|
||||
$("#database_rc").click(function() {
|
||||
if (this.value == 'sqlite'){
|
||||
$("#roundcube_external_db").hide();
|
||||
$("#roundcube_db_user,#roundcube_db_pw,#roundcube_db_url,#roundcube_db_name").prop('required',false);
|
||||
}
|
||||
else if ($("#webmail").val() == 'roundcube') {
|
||||
$("#roundcube_external_db").show();
|
||||
$("#roundcube_db_user,#roundcube_db_pw,#roundcube_db_url,#roundcube_db_name").prop('required',true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($('#enable_ipv6').prop('checked')) {
|
||||
$("#ipv6").show();
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
{% extends "bootstrap/base.html" %}
|
||||
{% import "macros.html" as macros %}
|
||||
{% from 'bootstrap/utils.html' import flashed_messages %}
|
||||
|
||||
{% block title %}Mailu setup{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
||||
<div id=no_java_script class="noscriptmsg">
|
||||
JavaScript is not enabled or JavaScript files were blocked. The Mailu setup site does not function when JavaScript is disabled.
|
||||
</div>
|
||||
|
||||
<div id="container" class="container" style="display:none;">
|
||||
<h1>Mailu configuration</h1>
|
||||
{{ flashed_messages() }}
|
||||
<p>
|
||||
Version
|
||||
<select id=version_select onchange="window.location.href=this.value;" class="btn btn-primary dropdown-toggle">
|
||||
@@ -27,4 +34,11 @@ For production scenarios we recommend to use the stable version.
|
||||
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='jquery.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='render.js') }}"></script>
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
@@ -9,7 +9,7 @@ bound to the internal IMAP and SMTP server for users to access their mailbox thr
|
||||
the Web. By exposing a complex application such as a Webmail, you should be aware of
|
||||
the security implications caused by such an increase of attack surface.<p>
|
||||
<div class="form-group">
|
||||
<label>Enable Web email client (and path to the Web email client)</label>
|
||||
<label>Enable Web email client</label>
|
||||
<br/>
|
||||
<select class="btn btn-primary dropdown-toggle" name="webmail_type" id="webmail">
|
||||
{% for webmailtype in ["none", "roundcube", "snappymail"] %}
|
||||
@@ -17,9 +17,6 @@ the security implications caused by such an increase of attack surface.<p>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p></p>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" name="webmail_path" id="webmail_path" style="display: none">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,12 +58,16 @@ the security implications caused by such an increase of attack surface.<p>
|
||||
Enable oletools
|
||||
</label>
|
||||
|
||||
<i>Oletools scans documents in email attachements for malicious macros. It has a much lower memory footprint than a full-fledged anti-virus.</i>
|
||||
<i>Oletools scans documents in email attachments for malicious macros. It has a much lower memory footprint than a full-fledged anti-virus.</i>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="checkbox" name="tika_enabled" value="true">
|
||||
Enable Tika
|
||||
</label>
|
||||
|
||||
<i>Tika enables the functionality for searching through attachments. Tika scans documents in email attachments, process (OCR, keyword extraction) and then index them in a way they can be efficiently searched. This requires significant resources (RAM, CPU and storage).</i>
|
||||
</div>
|
||||
|
||||
{% endcall %}
|
||||
|
||||
@@ -16,11 +16,10 @@ avoid generic all-interfaces addresses like <code>0.0.0.0</code> or <code>::</co
|
||||
<div class="form-group">
|
||||
<label>IPv4 listen address</label>
|
||||
<!-- Validates IPv4 address -->
|
||||
<input class="form-control" type="text" name="bind4" value="127.0.0.1"
|
||||
pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
|
||||
<label>Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)</label>
|
||||
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
|
||||
value="192.168.203.0/24">
|
||||
<input class="form-control" type="text" name="bind4" value="127.0.0.1" >
|
||||
<label>Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
|
||||
Usually the format is '*.*.*.0/24'</label>
|
||||
<input class="form-control" type="text" name="subnet" value="192.168.203.0/24">
|
||||
</div>
|
||||
|
||||
<div class="form-check form-check-inline">
|
||||
@@ -34,8 +33,7 @@ avoid generic all-interfaces addresses like <code>0.0.0.0</code> or <code>::</co
|
||||
<p><span class="label label-danger">Read this:</span> Docker currently does not expose the IPv6 ports properly, as it does not interface with <code>ip6tables</code>. Read <a href="https://mailu.io/{{ version }}/faq.html#how-to-make-ipv6-work">FAQ section</a> and be <b>very careful</b>. We do <b>NOT</b> recommend that you enable this!</p>
|
||||
<label>IPv6 listen address</label>
|
||||
<!-- Validates IPv6 address -->
|
||||
<input class="form-control" type="text" name="bind6" value="::1"
|
||||
pattern="^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$">
|
||||
<input class="form-control" type="text" name="bind6" value="::1" >
|
||||
<label>Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)</label>
|
||||
<input class="form-control" type="text" name="subnet6" required value="{{ subnet6 }}:beef::/64">
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<div class="form-group">
|
||||
<label>Mailu storage path: </label>
|
||||
<!-- Validates path -->
|
||||
<input class="form-control" type="text" name="root" value="/mailu" required pattern="^/[-_A-Za-z0-9]+(/[-_A-Za-z0-9]*)*">
|
||||
<input class="form-control" type="text" name="root" value="/mailu" required pattern="^\/[\-_A-Za-z0-9\.]+(\/[\-_A-Za-z0-9\.]*)*">
|
||||
</div>
|
||||
|
||||
<p>In the following sections we need to set the postmaster address. This is a combination of the <i>postmaster</i> local part and the <i>main mail domain</i>.
|
||||
The <i>main mail domain</i> is also used as </i>"server display name"</i>. This is the way the SMTP server identifies itself when connecting to others.
|
||||
The Postmaster will get an e-mail address <postmaster>@<main_domain>. This address will receive the DMARC "rua" and "ruf" reports.
|
||||
Or in plain english: if receivers start to classify your mail as spam, this postmaster will be informed.</p>
|
||||
Or in plain English: if receivers start to classify your mail as spam, this postmaster will be informed.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
@@ -60,8 +60,8 @@ Or in plain english: if receivers start to classify your mail as spam, this post
|
||||
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="checkbox" name="disable_statistics" value="True">
|
||||
Opt-out of statistics
|
||||
<input class="form-check-input" type="checkbox" name="statistics_enabled" value="True">
|
||||
Opt-in for anonymized statistics
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -82,8 +82,7 @@ manage your email domains, users, etc.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="admin_enabled" value="true" id="admin" checked>
|
||||
<label>Enable the admin UI (and path to the admin UI)</label>
|
||||
<input class="form-control" type="text" name="admin_path" id="admin_path" style="display: none">
|
||||
<label>Enable the admin UI</label>
|
||||
</div>
|
||||
|
||||
<p>The API interface is a RESTful API for changing the Mailu configuration.
|
||||
@@ -93,14 +92,10 @@ manage your email domains, users, etc.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="api_enabled" value="true" id="api_enabled" >
|
||||
<label>Enable the API (and path to the API)</label>
|
||||
<input class="form-control" type="text" name="api_path" id="api_path" style="display: none">
|
||||
<label>Enable the API</label>
|
||||
<br>
|
||||
<label name="api_token_label" id="api_token_label">API token</label>
|
||||
<input class="form-control" type="text" name="api_token" id="api_token" style="display: none" value="{{ secret(32) }}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
{% endcall %}
|
||||
|
||||
@@ -49,7 +49,6 @@ group "default" {
|
||||
|
||||
"webmail",
|
||||
|
||||
"antivirus",
|
||||
"fetchmail",
|
||||
"resolver",
|
||||
"traefik-certdumper",
|
||||
@@ -207,15 +206,6 @@ target "webmail" {
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# Optional images
|
||||
# -----------------------------------------------------------------------------------------
|
||||
target "antivirus" {
|
||||
inherits = ["defaults"]
|
||||
context = "optional/clamav/"
|
||||
contexts = {
|
||||
base = "docker-image://${DOCKER_ORG}/base:${MAILU_VERSION}"
|
||||
}
|
||||
tags = tag("clamav")
|
||||
}
|
||||
|
||||
target "fetchmail" {
|
||||
inherits = ["defaults"]
|
||||
context = "optional/fetchmail/"
|
||||
|
||||
@@ -45,7 +45,6 @@ group "default" {
|
||||
|
||||
"webmail",
|
||||
|
||||
"antivirus",
|
||||
"fetchmail",
|
||||
"resolver",
|
||||
"traefik-certdumper",
|
||||
@@ -201,15 +200,6 @@ target "webmail" {
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# Optional images
|
||||
# -----------------------------------------------------------------------------------------
|
||||
target "antivirus" {
|
||||
inherits = ["defaults"]
|
||||
context = "optional/clamav/"
|
||||
contexts = {
|
||||
base = "target:base"
|
||||
}
|
||||
tags = tag("clamav")
|
||||
}
|
||||
|
||||
target "fetchmail" {
|
||||
inherits = ["defaults"]
|
||||
context = "optional/fetchmail/"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com.txt"
|
||||
if [ $? -eq 99 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
if [ $? -ne 25 ]; then
|
||||
exit 1
|
||||
fi
|
||||
python3 tests/email_test.py message-PUA "tests/compose/filters/PotentiallyUnwanted.exe_"
|
||||
if [ $? -ne 25 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# GTUBE should be blocked, see https://rspamd.com/doc/gtube_patterns.html
|
||||
python3 tests/email_test.py "XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X"
|
||||
if [ $? -eq 25 ]; then
|
||||
exit 0
|
||||
else
|
||||
if [ $? -ne 25 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
BIN
tests/compose/filters/PotentiallyUnwanted.exe_
Normal file
BIN
tests/compose/filters/PotentiallyUnwanted.exe_
Normal file
Binary file not shown.
@@ -70,7 +70,7 @@ services:
|
||||
hostname: oletools
|
||||
restart: always
|
||||
networks:
|
||||
- noinet
|
||||
- oletools
|
||||
|
||||
antispam:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-local}
|
||||
@@ -78,21 +78,35 @@ services:
|
||||
env_file: mailu.env
|
||||
networks:
|
||||
- default
|
||||
- noinet
|
||||
- oletools
|
||||
- clamav
|
||||
volumes:
|
||||
- "/mailu/filter:/var/lib/rspamd"
|
||||
- "/mailu/dkim:/dkim"
|
||||
- "/mailu/overrides/rspamd:/etc/rspamd/override.d"
|
||||
depends_on:
|
||||
- front
|
||||
- antivirus
|
||||
- oletools
|
||||
|
||||
# Optional services
|
||||
antivirus:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-local}
|
||||
image: clamav/clamav-debian:1.2.0-6
|
||||
restart: always
|
||||
env_file: mailu.env
|
||||
logging:
|
||||
driver: journald
|
||||
options:
|
||||
tag: mailu-clamav
|
||||
networks:
|
||||
- clamav
|
||||
volumes:
|
||||
- "/mailu/filter:/data"
|
||||
- "/mailu/filter/clamav:/var/lib/clamav"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kill -0 `cat /tmp/clamd.pid` && kill -0 `cat /tmp/freshclam.pid`"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
resolver:
|
||||
image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-local}
|
||||
@@ -112,6 +126,8 @@ networks:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 192.168.203.0/24
|
||||
noinet:
|
||||
clamav:
|
||||
driver: bridge
|
||||
oletools:
|
||||
driver: bridge
|
||||
internal: true
|
||||
|
||||
@@ -21,19 +21,31 @@ if len(sys.argv) == 3:
|
||||
part.add_header('Content-Disposition', "attachment; filename=%s" % ntpath.basename(sys.argv[2]))
|
||||
msg.attach(part)
|
||||
|
||||
try:
|
||||
smtp_server = smtplib.SMTP('localhost')
|
||||
smtp_server.set_debuglevel(1)
|
||||
smtp_server.connect('localhost', 587)
|
||||
smtp_server.ehlo()
|
||||
smtp_server.starttls()
|
||||
smtp_server.ehlo()
|
||||
smtp_server.login("admin@mailu.io", "password")
|
||||
for i in range(5):
|
||||
try:
|
||||
smtp_server = smtplib.SMTP('localhost')
|
||||
smtp_server.set_debuglevel(1)
|
||||
smtp_server.connect('localhost', 587)
|
||||
smtp_server.ehlo()
|
||||
smtp_server.starttls()
|
||||
smtp_server.ehlo()
|
||||
smtp_server.login("admin@mailu.io", "password")
|
||||
|
||||
smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string())
|
||||
smtp_server.quit()
|
||||
except:
|
||||
sys.exit(25)
|
||||
smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string())
|
||||
smtp_server.quit()
|
||||
except smtplib.SMTPRecipientsRefused:
|
||||
sys.exit(25)
|
||||
except smtplib.SMTPDataError as e:
|
||||
if e.smtp_code == 451:
|
||||
print(f"Not ready attempt {i}")
|
||||
time.sleep(5)
|
||||
continue
|
||||
if e.smtp_code >= 500 and e.smtp_code <600:
|
||||
sys.exit(25)
|
||||
sys.exit(2525)
|
||||
except:
|
||||
sys.exit(2525)
|
||||
break
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
|
||||
1
towncrier/newsfragments/2059.misc
Normal file
1
towncrier/newsfragments/2059.misc
Normal file
@@ -0,0 +1 @@
|
||||
Switch to upstream's clamav image
|
||||
1
towncrier/newsfragments/2824.feature
Normal file
1
towncrier/newsfragments/2824.feature
Normal file
@@ -0,0 +1 @@
|
||||
Enhance RESTful API user retrieval with quota used bytes. This is the current size of the user's email box in bytes.
|
||||
5
towncrier/newsfragments/2890.bugfix
Normal file
5
towncrier/newsfragments/2890.bugfix
Normal file
@@ -0,0 +1,5 @@
|
||||
Setup:
|
||||
Regular expression for checking the Mailu storage path was invalid.
|
||||
Added checks to make sure JavaScript is enabled and that all JS files could be loaded. The setup site malfunctions if this is not the case.
|
||||
Added server side validation of entered values in setup.
|
||||
Simplified setup by removing the settings for configuring the WEB_* settings. Advanced users can still modify mailu.env.
|
||||
1
towncrier/newsfragments/2918.misc
Normal file
1
towncrier/newsfragments/2918.misc
Normal file
@@ -0,0 +1 @@
|
||||
Upgrade dovecot to ensure we can proxy ipv6 via XCLIENT.
|
||||
1
towncrier/newsfragments/2928.bugfix
Normal file
1
towncrier/newsfragments/2928.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
fix fetchmail when used with POP3: disregard "folders"
|
||||
3
towncrier/newsfragments/2934.bugfix
Normal file
3
towncrier/newsfragments/2934.bugfix
Normal file
@@ -0,0 +1,3 @@
|
||||
Upgrade to alpine 3.18.4: this will fix a bug whereby musl wasn't retrying using TCP when it received truncated DNS replies from its upstream. In practice, this has been seen in the wild when postfix complains of:
|
||||
|
||||
"Host or domain name not found. Name service error for name=outlook-com.olc.protection.outlook.com type=AAAA: Host found but no data record of requested type"
|
||||
1
towncrier/newsfragments/2935.misc
Normal file
1
towncrier/newsfragments/2935.misc
Normal file
@@ -0,0 +1 @@
|
||||
Add Persian (aka Farsi) Translation
|
||||
1
towncrier/newsfragments/2936.feature
Normal file
1
towncrier/newsfragments/2936.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add ukrainian translation
|
||||
2
towncrier/newsfragments/2937.bugfix
Normal file
2
towncrier/newsfragments/2937.bugfix
Normal file
@@ -0,0 +1,2 @@
|
||||
forbidden_file_extension.map could not be overridden. This file can be overriden to tweak with file extensions are allowed.
|
||||
The instructions on https://mailu.io/master/antispam.html#can-i-change-the-list-of-authorized-file-attachments work again.
|
||||
4
towncrier/newsfragments/2939.bugfix
Normal file
4
towncrier/newsfragments/2939.bugfix
Normal file
@@ -0,0 +1,4 @@
|
||||
Fixed log filter not filtering out log messages for dovecot/nginx/postfix.
|
||||
Fixed postfix not logging to standard out.
|
||||
Fixed not all containers logging to journald.
|
||||
Removed POSTFIX_LOG_FILE functionality. Added documentation on how to achieve the same (log to file) via journald & rsyslogd (see new FAQ entry 'How can I view and export the logs of a Mailu container?').
|
||||
1
towncrier/newsfragments/2945.bugfix
Normal file
1
towncrier/newsfragments/2945.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Upgrade webmails: roundcube 1.6.3, rcmcarddav 5.1.0, snappymail 2.28.4
|
||||
1
towncrier/newsfragments/2948.feature
Normal file
1
towncrier/newsfragments/2948.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add Traditional Chinese translation
|
||||
1
towncrier/newsfragments/2950.misc
Normal file
1
towncrier/newsfragments/2950.misc
Normal file
@@ -0,0 +1 @@
|
||||
Upgrade to snuffleupagus 0.10.0
|
||||
1
towncrier/newsfragments/2955.misc
Normal file
1
towncrier/newsfragments/2955.misc
Normal file
@@ -0,0 +1 @@
|
||||
Remove the version pinning on hardened malloc
|
||||
3
towncrier/newsfragments/2959.bugfix
Normal file
3
towncrier/newsfragments/2959.bugfix
Normal file
@@ -0,0 +1,3 @@
|
||||
Update hardened malloc as the original package is not available from alpine anymore.
|
||||
The newer version of hardened malloc requires AVX2: Disable it by default at startup and hint in the logs when it should be enabled instead.
|
||||
Upgrade snappymail to v2.29.1
|
||||
1
towncrier/newsfragments/2962.bugfix
Normal file
1
towncrier/newsfragments/2962.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix letsencrypt on master
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user