Merge branch 'master' into delete-disable
commit
31faee4218
@ -0,0 +1,32 @@
|
||||
from flask import redirect, url_for, Blueprint
|
||||
from flask_restx import apidoc
|
||||
from . import v1 as APIv1
|
||||
|
||||
def register(app, web_api_root):
|
||||
|
||||
APIv1.app = app
|
||||
# register api bluprint(s)
|
||||
apidoc.apidoc.url_prefix = f'{web_api_root}/v{int(APIv1.VERSION)}'
|
||||
APIv1.api_token = app.config['API_TOKEN']
|
||||
if app.config['API_TOKEN'] != '':
|
||||
app.register_blueprint(APIv1.blueprint, url_prefix=f'{web_api_root}/v{int(APIv1.VERSION)}')
|
||||
|
||||
# add redirect to current api version
|
||||
redirect_api = Blueprint('redirect_api', __name__)
|
||||
@redirect_api.route('/')
|
||||
def redir():
|
||||
return redirect(url_for(f'{APIv1.blueprint.name}.root'))
|
||||
app.register_blueprint(redirect_api, url_prefix=f'{web_api_root}')
|
||||
|
||||
# swagger ui config
|
||||
app.config.SWAGGER_UI_DOC_EXPANSION = 'list'
|
||||
app.config.SWAGGER_UI_OPERATION_ID = True
|
||||
app.config.SWAGGER_UI_REQUEST_DURATION = True
|
||||
app.config.RESTX_MASK_SWAGGER = False
|
||||
else:
|
||||
api = Blueprint('api', __name__)
|
||||
@api.route('/', defaults={'path': ''})
|
||||
@api.route('/<path:path>')
|
||||
def api_token_missing(path):
|
||||
return "<p>Error: API_TOKEN is not configured</p>", 500
|
||||
app.register_blueprint(api, url_prefix=f'{web_api_root}')
|
@ -0,0 +1,42 @@
|
||||
from .. import models, utils
|
||||
from . import v1
|
||||
from flask import request
|
||||
import flask
|
||||
import hmac
|
||||
from functools import wraps
|
||||
from flask_restx import abort
|
||||
from sqlalchemy.sql.expression import label
|
||||
|
||||
def fqdn_in_use(name):
|
||||
d = models.db.session.query(label('name', models.Domain.name))
|
||||
a = models.db.session.query(label('name', models.Alternative.name))
|
||||
r = models.db.session.query(label('name', models.Relay.name))
|
||||
u = d.union_all(a).union_all(r).filter_by(name=name)
|
||||
if models.db.session.query(u.exists()).scalar():
|
||||
return True
|
||||
return False
|
||||
|
||||
""" Decorator for validating api token for authentication """
|
||||
def api_token_authorization(func):
|
||||
@wraps(func)
|
||||
def decorated_function(*args, **kwds):
|
||||
client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)
|
||||
if utils.limiter.should_rate_limit_ip(client_ip):
|
||||
abort(429, 'Too many attempts from your IP (rate-limit)' )
|
||||
if not request.headers.get('Authorization'):
|
||||
abort(401, 'A valid Bearer token is expected which is provided as request header')
|
||||
#Client provides 'Authentication: Bearer <token>'
|
||||
if (' ' in request.headers.get('Authorization')
|
||||
and not hmac.compare_digest(request.headers.get('Authorization'), 'Bearer ' + v1.api_token)):
|
||||
utils.limiter.rate_limit_ip(client_ip)
|
||||
flask.current_app.logger.warn(f'Invalid API token provided by {client_ip}.')
|
||||
abort(403, 'A valid Bearer token is expected which is provided as request header')
|
||||
#Client provides 'Authentication: <token>'
|
||||
elif (' ' not in request.headers.get('Authorization')
|
||||
and not hmac.compare_digest(request.headers.get('Authorization'), v1.api_token)):
|
||||
utils.limiter.rate_limit_ip(client_ip)
|
||||
flask.current_app.logger.warn(f'Invalid API token provided by {client_ip}.')
|
||||
abort(403, 'A valid Bearer token is expected which is provided as request header')
|
||||
flask.current_app.logger.info(f'Valid API token provided by {client_ip}.')
|
||||
return func(*args, **kwds)
|
||||
return decorated_function
|
@ -0,0 +1,43 @@
|
||||
from flask import Blueprint
|
||||
from flask_restx import Api, fields
|
||||
|
||||
|
||||
VERSION = 1.0
|
||||
api_token = None
|
||||
|
||||
blueprint = Blueprint(f'api_v{int(VERSION)}', __name__)
|
||||
|
||||
authorization = {
|
||||
'Bearer': {
|
||||
'type': 'apiKey',
|
||||
'in': 'header',
|
||||
'name': 'Authorization'
|
||||
}
|
||||
}
|
||||
|
||||
api = Api(
|
||||
blueprint, version=f'{VERSION:.1f}',
|
||||
title='Mailu API', default_label='Mailu',
|
||||
validate=True,
|
||||
authorizations=authorization,
|
||||
security='Bearer',
|
||||
doc='/'
|
||||
)
|
||||
|
||||
response_fields = api.model('Response', {
|
||||
'code': fields.Integer,
|
||||
'message': fields.String,
|
||||
})
|
||||
|
||||
error_fields = api.model('Error', {
|
||||
'errors': fields.Nested(api.model('Error_Key', {
|
||||
'key': fields.String,
|
||||
'message':fields.String
|
||||
})),
|
||||
'message': fields.String,
|
||||
})
|
||||
|
||||
from . import domains
|
||||
from . import alias
|
||||
from . import relay
|
||||
from . import user
|
@ -0,0 +1,126 @@
|
||||
from flask_restx import Resource, fields, marshal
|
||||
from . import api, response_fields
|
||||
from .. import common
|
||||
from ... import models
|
||||
|
||||
db = models.db
|
||||
|
||||
alias = api.namespace('alias', description='Alias operations')
|
||||
|
||||
alias_fields_update = alias.model('AliasUpdate', {
|
||||
'comment': fields.String(description='a comment'),
|
||||
'destination': fields.List(fields.String(description='alias email address', example='user@example.com')),
|
||||
'wildcard': fields.Boolean(description='enable SQL Like wildcard syntax')
|
||||
})
|
||||
|
||||
alias_fields = alias.inherit('Alias',alias_fields_update, {
|
||||
'email': fields.String(description='the alias email address', example='user@example.com', required=True),
|
||||
'destination': fields.List(fields.String(description='alias email address', example='user@example.com', required=True)),
|
||||
|
||||
})
|
||||
|
||||
|
||||
@alias.route('')
|
||||
class Aliases(Resource):
|
||||
@alias.doc('list_alias')
|
||||
@alias.marshal_with(alias_fields, as_list=True, skip_none=True, mask=None)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
""" List aliases """
|
||||
return models.Alias.query.all()
|
||||
|
||||
@alias.doc('create_alias')
|
||||
@alias.expect(alias_fields)
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.response(409, 'Duplicate alias', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create a new alias """
|
||||
data = api.payload
|
||||
|
||||
alias_found = models.Alias.query.filter_by(email = data['email']).first()
|
||||
if alias_found:
|
||||
return { 'code': 409, 'message': f'Duplicate alias {data["email"]}'}, 409
|
||||
|
||||
alias_model = models.Alias(email=data["email"],destination=data['destination'])
|
||||
if 'comment' in data:
|
||||
alias_model.comment = data['comment']
|
||||
if 'wildcard' in data:
|
||||
alias_model.wildcard = data['wildcard']
|
||||
db.session.add(alias_model)
|
||||
db.session.commit()
|
||||
|
||||
return {'code': 200, 'message': f'Alias {data["email"]} to destination {data["destination"]} has been created'}, 200
|
||||
|
||||
@alias.route('/<string:alias>')
|
||||
class Alias(Resource):
|
||||
@alias.doc('find_alias')
|
||||
@alias.response(200, 'Success', alias_fields)
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, alias):
|
||||
""" Find alias """
|
||||
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||
if alias_found is None:
|
||||
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||
else:
|
||||
return marshal(alias_found,alias_fields), 200
|
||||
|
||||
@alias.doc('update_alias')
|
||||
@alias.expect(alias_fields_update)
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.response(400, 'Input validation exception', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def patch(self, alias):
|
||||
""" Update alias """
|
||||
data = api.payload
|
||||
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||
if alias_found is None:
|
||||
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||
if 'comment' in data:
|
||||
alias_found.comment = data['comment']
|
||||
if 'destination' in data:
|
||||
alias_found.destination = data['destination']
|
||||
if 'wildcard' in data:
|
||||
alias_found.wildcard = data['wildcard']
|
||||
db.session.add(alias_found)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Alias {alias} has been updated'}
|
||||
|
||||
@alias.doc('delete_alias')
|
||||
@alias.response(200, 'Success', response_fields)
|
||||
@alias.response(404, 'Alias not found', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, alias):
|
||||
""" Delete alias """
|
||||
alias_found = models.Alias.query.filter_by(email = alias).first()
|
||||
if alias_found is None:
|
||||
return { 'code': 404, 'message': f'Alias {alias} cannot be found'}, 404
|
||||
db.session.delete(alias_found)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Alias {alias} has been deleted'}, 200
|
||||
|
||||
@alias.route('/destination/<string:domain>')
|
||||
class AliasWithDest(Resource):
|
||||
@alias.doc('find_alias_filter_domain')
|
||||
@alias.response(200, 'Success', alias_fields)
|
||||
@alias.response(404, 'Alias or domain not found', response_fields)
|
||||
@alias.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" Find aliases of domain """
|
||||
domain_found = models.Domain.query.filter_by(name=domain).first()
|
||||
if domain_found is None:
|
||||
return { 'code': 404, 'message': f'Domain {domain} cannot be found'}, 404
|
||||
aliases_found = domain_found.aliases
|
||||
if aliases_found.count == 0:
|
||||
return { 'code': 404, 'message': f'No alias can be found for domain {domain}'}, 404
|
||||
else:
|
||||
return marshal(aliases_found, alias_fields), 200
|
@ -0,0 +1,410 @@
|
||||
import validators
|
||||
from flask_restx import Resource, fields, marshal
|
||||
from . import api, response_fields, user
|
||||
from .. import common
|
||||
from ... import models
|
||||
|
||||
db = models.db
|
||||
|
||||
dom = api.namespace('domain', description='Domain operations')
|
||||
alt = api.namespace('alternative', description='Alternative operations')
|
||||
|
||||
domain_fields = api.model('Domain', {
|
||||
'name': fields.String(description='FQDN (e.g. example.com)', example='example.com', required=True),
|
||||
'comment': fields.String(description='a comment'),
|
||||
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||
})
|
||||
|
||||
domain_fields_update = api.model('DomainUpdate', {
|
||||
'comment': fields.String(description='a comment'),
|
||||
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||
})
|
||||
|
||||
domain_fields_get = api.model('DomainGet', {
|
||||
'name': fields.String(description='FQDN (e.g. example.com)', example='example.com', required=True),
|
||||
'comment': fields.String(description='a comment'),
|
||||
'max_users': fields.Integer(description='maximum number of users', min=-1, default=-1),
|
||||
'max_aliases': fields.Integer(description='maximum number of aliases', min=-1, default=-1),
|
||||
'max_quota_bytes': fields.Integer(description='maximum quota for mailbox', min=0),
|
||||
'signup_enabled': fields.Boolean(description='allow signup'),
|
||||
'alternatives': fields.List(fields.String(attribute='name', description='FQDN', example='example2.com')),
|
||||
'dns_autoconfig': fields.List(fields.String(description='DNS client auto-configuration entry')),
|
||||
'dns_mx': fields.String(Description='MX record for domain'),
|
||||
'dns_spf': fields.String(Description='SPF record for domain'),
|
||||
'dns_dkim': fields.String(Description='DKIM record for domain'),
|
||||
'dns_dmarc': fields.String(Description='DMARC record for domain'),
|
||||
'dns_dmarc_report': fields.String(Description='DMARC report record for domain'),
|
||||
'dns_tlsa': fields.String(Description='TLSA record for domain'),
|
||||
})
|
||||
|
||||
domain_fields_dns = api.model('DomainDNS', {
|
||||
'dns_autoconfig': fields.List(fields.String(description='DNS client auto-configuration entry')),
|
||||
'dns_mx': fields.String(Description='MX record for domain'),
|
||||
'dns_spf': fields.String(Description='SPF record for domain'),
|
||||
'dns_dkim': fields.String(Description='DKIM record for domain'),
|
||||
'dns_dmarc': fields.String(Description='DMARC record for domain'),
|
||||
'dns_dmarc_report': fields.String(Description='DMARC report record for domain'),
|
||||
'dns_tlsa': fields.String(Description='TLSA record for domain'),
|
||||
})
|
||||
|
||||
manager_fields = api.model('Manager', {
|
||||
'domain_name': fields.String(description='domain managed by manager'),
|
||||
'user_email': fields.String(description='email address of manager'),
|
||||
})
|
||||
|
||||
manager_fields_create = api.model('ManagerCreate', {
|
||||
'user_email': fields.String(description='email address of manager', required=True),
|
||||
})
|
||||
|
||||
alternative_fields_update = api.model('AlternativeDomainUpdate', {
|
||||
'domain': fields.String(description='domain FQDN', example='example.com', required=False),
|
||||
})
|
||||
|
||||
alternative_fields = api.model('AlternativeDomain', {
|
||||
'name': fields.String(description='alternative FQDN', example='example2.com', required=True),
|
||||
'domain': fields.String(description='domain FQDN', example='example.com', required=True),
|
||||
})
|
||||
|
||||
|
||||
@dom.route('')
|
||||
class Domains(Resource):
|
||||
@dom.doc('list_domain')
|
||||
@dom.marshal_with(domain_fields_get, as_list=True, skip_none=True, mask=None)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
""" List domains """
|
||||
return models.Domain.query.all()
|
||||
|
||||
@dom.doc('create_domain')
|
||||
@dom.expect(domain_fields)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create a new domain """
|
||||
data = api.payload
|
||||
if not validators.domain(data['name']):
|
||||
return { 'code': 400, 'message': f'Domain {data["name"]} is not a valid domain'}, 400
|
||||
|
||||
if common.fqdn_in_use(data['name']):
|
||||
return { 'code': 409, 'message': f'Duplicate domain name {data["name"]}'}, 409
|
||||
if 'alternatives' in data:
|
||||
#check if duplicate alternatives are supplied
|
||||
if [x for x in data['alternatives'] if data['alternatives'].count(x) >= 2]:
|
||||
return { 'code': 409, 'message': f'Duplicate alternative domain names in request' }, 409
|
||||
for item in data['alternatives']:
|
||||
if common.fqdn_in_use(item):
|
||||
return { 'code': 409, 'message': f'Duplicate alternative domain name {item}' }, 409
|
||||
if not validators.domain(item):
|
||||
return { 'code': 400, 'message': f'Alternative domain {item} is not a valid domain'}, 400
|
||||
for item in data['alternatives']:
|
||||
alternative = models.Alternative(name=item, domain_name=data['name'])
|
||||
models.db.session.add(alternative)
|
||||
domain_new = models.Domain(name=data['name'])
|
||||
if 'comment' in data:
|
||||
domain_new.comment = data['comment']
|
||||
if 'max_users' in data:
|
||||
domain_new.comment = data['max_users']
|
||||
if 'max_aliases' in data:
|
||||
domain_new.comment = data['max_aliases']
|
||||
if 'max_quota_bytes' in data:
|
||||
domain_new.comment = data['max_quota_bytes']
|
||||
if 'signup_enabled' in data:
|
||||
domain_new.comment = data['signup_enabled']
|
||||
models.db.session.add(domain_new)
|
||||
#apply the changes
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Domain {data["name"]} has been created'}, 200
|
||||
|
||||
@dom.route('/<domain>')
|
||||
class Domain(Resource):
|
||||
|
||||
@dom.doc('find_domain')
|
||||
@dom.response(200, 'Success', domain_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" Find domain by name """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
return marshal(domain_found, domain_fields_get), 200
|
||||
|
||||
@dom.doc('update_domain')
|
||||
@dom.expect(domain_fields_update)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.response(409, 'Duplicate domain/alternative name', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def patch(self, domain):
|
||||
""" Update an existing domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {data["name"]} does not exist'}, 404
|
||||
data = api.payload
|
||||
|
||||
if 'alternatives' in data:
|
||||
#check if duplicate alternatives are supplied
|
||||
if [x for x in data['alternatives'] if data['alternatives'].count(x) >= 2]:
|
||||
return { 'code': 409, 'message': f'Duplicate alternative domain names in request' }, 409
|
||||
for item in data['alternatives']:
|
||||
if common.fqdn_in_use(item):
|
||||
return { 'code': 409, 'message': f'Duplicate alternative domain name {item}' }, 409
|
||||
if not validators.domain(item):
|
||||
return { 'code': 400, 'message': f'Alternative domain {item} is not a valid domain'}, 400
|
||||
for item in data['alternatives']:
|
||||
alternative = models.Alternative(name=item, domain_name=data['name'])
|
||||
models.db.session.add(alternative)
|
||||
|
||||
if 'comment' in data:
|
||||
domain_found.comment = data['comment']
|
||||
if 'max_users' in data:
|
||||
domain_found.comment = data['max_users']
|
||||
if 'max_aliases' in data:
|
||||
domain_found.comment = data['max_aliases']
|
||||
if 'max_quota_bytes' in data:
|
||||
domain_found.comment = data['max_quota_bytes']
|
||||
if 'signup_enabled' in data:
|
||||
domain_found.comment = data['signup_enabled']
|
||||
models.db.session.add(domain_found)
|
||||
|
||||
#apply the changes
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Domain {domain} has been updated'}, 200
|
||||
|
||||
@dom.doc('delete_domain')
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, domain):
|
||||
""" Delete domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
db.session.delete(domain_found)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Domain {domain} has been deleted'}, 200
|
||||
|
||||
@dom.route('/<domain>/dkim')
|
||||
class Domain(Resource):
|
||||
@dom.doc('generate_dkim')
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self, domain):
|
||||
""" Generate new DKIM/DMARC keys for domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
domain_found.generate_dkim_key()
|
||||
domain_found.save_dkim_key()
|
||||
return {'code': 200, 'message': f'DKIM/DMARC keys have been generated for domain {domain}'}, 200
|
||||
|
||||
@dom.route('/<domain>/manager')
|
||||
class Manager(Resource):
|
||||
@dom.doc('list_managers')
|
||||
@dom.marshal_with(manager_fields, as_list=True, skip_none=True, mask=None)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" List managers of domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
domain = models.Domain.query.filter_by(name=domain)
|
||||
return domain.managers
|
||||
|
||||
@dom.doc('create_manager')
|
||||
@dom.expect(manager_fields_create)
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'User or domain not found', response_fields)
|
||||
@dom.response(409, 'Duplicate domain manager', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self, domain):
|
||||
""" Create a new domain manager """
|
||||
data = api.payload
|
||||
if not validators.email(data['user_email']):
|
||||
return {'code': 400, 'message': f'Invalid email address {data["user_email"]}'}, 400
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
user = models.User.query.get(data['user_email'])
|
||||
if not user:
|
||||
return { 'code': 404, 'message': f'User {data["user_email"]} does not exist'}, 404
|
||||
if user in domain.managers:
|
||||
return {'code': 409, 'message': f'User {data["user_email"]} is already a manager of the domain {domain} '}, 409
|
||||
domain.managers.append(user)
|
||||
models.db.session.commit()
|
||||
return {'code': 200, 'message': f'User {data["user_email"]} has been added as manager of the domain {domain} '},200
|
||||
|
||||
@dom.route('/<domain>/manager/<email>')
|
||||
class Domain(Resource):
|
||||
@dom.doc('find_manager')
|
||||
@dom.response(200, 'Success', manager_fields)
|
||||
@dom.response(404, 'Manager not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain, email):
|
||||
""" Find manager by email address """
|
||||
if not validators.email(email):
|
||||
return {'code': 400, 'message': f'Invalid email address {email}'}, 400
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
user = models.User.query.get(email)
|
||||
if not user:
|
||||
return { 'code': 404, 'message': f'User {email} does not exist'}, 404
|
||||
if user in domain.managers:
|
||||
for manager in domain.managers:
|
||||
if manager.email == email:
|
||||
return marshal(manager, manager_fields),200
|
||||
else:
|
||||
return { 'code': 404, 'message': f'User {email} is not a manager of the domain {domain}'}, 404
|
||||
|
||||
|
||||
@dom.doc('delete_manager')
|
||||
@dom.response(200, 'Success', response_fields)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Manager not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, domain, email):
|
||||
if not validators.email(email):
|
||||
return {'code': 400, 'message': f'Invalid email address {email}'}, 400
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain = models.Domain.query.get(domain)
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
user = models.User.query.get(email)
|
||||
if not user:
|
||||
return { 'code': 404, 'message': f'User {email} does not exist'}, 404
|
||||
if user in domain.managers:
|
||||
domain.managers.remove(user)
|
||||
models.db.session.commit()
|
||||
return {'code': 200, 'message': f'User {email} has been removed as a manager of the domain {domain} '},200
|
||||
else:
|
||||
return { 'code': 404, 'message': f'User {email} is not a manager of the domain {domain}'}, 404
|
||||
|
||||
@dom.route('/<domain>/users')
|
||||
class User(Resource):
|
||||
@dom.doc('list_user_domain')
|
||||
@dom.marshal_with(user.user_fields_get, as_list=True, skip_none=True, mask=None)
|
||||
@dom.response(400, 'Input validation exception', response_fields)
|
||||
@dom.response(404, 'Domain not found', response_fields)
|
||||
@dom.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, domain):
|
||||
""" List users from domain """
|
||||
if not validators.domain(domain):
|
||||
return { 'code': 400, 'message': f'Domain {domain} is not a valid domain'}, 400
|
||||
domain_found = models.Domain.query.get(domain)
|
||||
if not domain_found:
|
||||
return { 'code': 404, 'message': f'Domain {domain} does not exist'}, 404
|
||||
return models.User.query.filter_by(domain=domain_found).all()
|
||||
|
||||
@alt.route('')
|
||||
class Alternatives(Resource):
|
||||
|
||||
@alt.doc('list_alternative')
|
||||
@alt.marshal_with(alternative_fields, as_list=True, skip_none=True, mask=None)
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
""" List alternatives """
|
||||
return models.Alternative.query.all()
|
||||
|
||||
|
||||
@alt.doc('create_alternative')
|
||||
@alt.expect(alternative_fields)
|
||||
@alt.response(200, 'Success', response_fields)
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.response(404, 'Domain not found or missing', response_fields)
|
||||
@alt.response(409, 'Duplicate alternative domain name', response_fields)
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create new alternative (for domain) """
|
||||
data = api.payload
|
||||
if not validators.domain(data['name']):
|
||||
return { 'code': 400, 'message': f'Alternative domain {data["name"]} is not a valid domain'}, 400
|
||||
if not validators.domain(data['domain']):
|
||||
return { 'code': 400, 'message': f'Domain {data["domain"]} is not a valid domain'}, 400
|
||||
domain = models.Domain.query.get(data['domain'])
|
||||
if not domain:
|
||||
return { 'code': 404, 'message': f'Domain {data["domain"]} does not exist'}, 404
|
||||
if common.fqdn_in_use(data['name']):
|
||||
return { 'code': 409, 'message': f'Duplicate alternative domain name {data["name"]}'}, 409
|
||||
|
||||
alternative = models.Alternative(name=data['name'], domain_name=data['domain'])
|
||||
models.db.session.add(alternative)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Alternative {data["name"]} for domain {data["domain"]} has been created'}, 200
|
||||
|
||||
@alt.route('/<string:alt>')
|
||||
class Alternative(Resource):
|
||||
@alt.doc('find_alternative')
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, alt):
|
||||
""" Find alternative (of domain) """
|
||||
if not validators.domain(alt):
|
||||
return { 'code': 400, 'message': f'Alternative domain {alt} is not a valid domain'}, 400
|
||||
alternative = models.Alternative.query.filter_by(name=alt).first()
|
||||
if not alternative:
|
||||
return{ 'code': 404, 'message': f'Alternative domain {alt} does not exist'}, 404
|
||||
return marshal(alternative, alternative_fields), 200
|
||||
|
||||
@alt.doc('delete_alternative')
|
||||
@alt.response(200, 'Success', response_fields)
|
||||
@alt.response(400, 'Input validation exception', response_fields)
|
||||
@alt.response(404, 'Alternative/Domain not found or missing', response_fields)
|
||||
@alt.response(409, 'Duplicate domain name', response_fields)
|
||||
@alt.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, alt):
|
||||
""" Delete alternative (for domain) """
|
||||
if not validators.domain(alt):
|
||||
return { 'code': 400, 'message': f'Alternative domain {alt} is not a valid domain'}, 400
|
||||
alternative = models.Alternative.query.filter_by(name=alt).scalar()
|
||||
if not alternative:
|
||||
return { 'code': 404, 'message': f'Alternative domain {alt} does not exist'}, 404
|
||||
domain = alternative.domain_name
|
||||
db.session.delete(alternative)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Alternative {alt} for domain {domain} has been deleted'}, 200
|
@ -0,0 +1,118 @@
|
||||
from flask_restx import Resource, fields, marshal
|
||||
import validators
|
||||
|
||||
from . import api, response_fields
|
||||
from .. import common
|
||||
from ... import models
|
||||
|
||||
db = models.db
|
||||
|
||||
relay = api.namespace('relay', description='Relay operations')
|
||||
|
||||
relay_fields = api.model('Relay', {
|
||||
'name': fields.String(description='relayed domain name', example='example.com', required=True),
|
||||
'smtp': fields.String(description='remote host', example='example.com', required=False),
|
||||
'comment': fields.String(description='a comment', required=False)
|
||||
})
|
||||
|
||||
relay_fields_update = api.model('RelayUpdate', {
|
||||
'smtp': fields.String(description='remote host', example='example.com', required=False),
|
||||
'comment': fields.String(description='a comment', required=False)
|
||||
})
|
||||
|
||||
@relay.route('')
|
||||
class Relays(Resource):
|
||||
@relay.doc('list_relays')
|
||||
@relay.marshal_with(relay_fields, as_list=True, skip_none=True, mask=None)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self):
|
||||
"List relays"
|
||||
return models.Relay.query.all()
|
||||
|
||||
@relay.doc('create_relay')
|
||||
@relay.expect(relay_fields)
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception')
|
||||
@relay.response(409, 'Duplicate relay', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def post(self):
|
||||
""" Create relay """
|
||||
data = api.payload
|
||||
|
||||
if not validators.domain(name):
|
||||
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||
|
||||
if common.fqdn_in_use(data['name']):
|
||||
return { 'code': 409, 'message': f'Duplicate domain {data["name"]}'}, 409
|
||||
relay_model = models.Relay(name=data['name'])
|
||||
if 'smtp' in data:
|
||||
relay_model.smtp = data['smtp']
|
||||
if 'comment' in data:
|
||||
relay_model.comment = data['comment']
|
||||
db.session.add(relay_model)
|
||||
db.session.commit()
|
||||
return {'code': 200, 'message': f'Relayed domain {data["name"]} has been created'}, 200
|
||||
|
||||
@relay.route('/<string:name>')
|
||||
class Relay(Resource):
|
||||
@relay.doc('find_relay')
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def get(self, name):
|
||||
""" Find relay """
|
||||
if not validators.domain(name):
|
||||
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||
|
||||
relay_found = models.Relay.query.filter_by(name=name).first()
|
||||
if relay_found is None:
|
||||
return { 'code': 404, 'message': f'Relayed domain {name} cannot be found'}, 404
|
||||
return marshal(relay_found, relay_fields), 200
|
||||
|
||||
@relay.doc('update_relay')
|
||||
@relay.expect(relay_fields_update)
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.response(409, 'Duplicate relay', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def patch(self, name):
|
||||
""" Update relay """
|
||||
data = api.payload
|
||||
|
||||
if not validators.domain(name):
|
||||
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||
|
||||
relay_found = models.Relay.query.filter_by(name=name).first()
|
||||
if relay_found is None:
|
||||
return { 'code': 404, 'message': f'Relayed domain {name} cannot be found'}, 404
|
||||
|
||||
if 'smtp' in data:
|
||||
relay_found.smtp = data['smtp']
|
||||
if 'comment' in data:
|
||||
relay_found.comment = data['comment']
|
||||
db.session.add(relay_found)
|
||||
db.session.commit()
|
||||
return { 'code': 200, 'message': f'Relayed domain {name} has been updated'}, 200
|
||||
|
||||
|
||||
@relay.doc('delete_relay')
|
||||
@relay.response(200, 'Success', response_fields)
|
||||
@relay.response(400, 'Input validation exception', response_fields)
|
||||
@relay.response(404, 'Relay not found', response_fields)
|
||||
@relay.doc(security='Bearer')
|
||||
@common.api_token_authorization
|
||||
def delete(self, name):
|
||||
""" Delete relay """
|
||||
if not validators.domain(name):
|
||||
return { 'code': 400, 'message': f'Relayed domain {name} is not a valid domain'}, 400
|
||||
relay_found = models.Relay.query.filter_by(name=name).first()
|
||||
if relay_found is None:
|
||||
return { 'code': 404, 'message': f'Relayed domain {name} cannot be found'}, 404
|
||||
db.session.delete(relay_found)
|
||||
db.session.commit()
|
||||
return { 'code': 200, 'message': f'Relayed domain {name} has been deleted'}, 200
|
@ -0,0 +1,733 @@
|
||||
# Czech translations for Mailu.io.
|
||||
# Copyright (C) 2023 S474N
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# S474N <translate@s474n.com>, 2023.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: translate@s474n.com\n"
|
||||
"POT-Creation-Date: 2022-05-22 18:47+0200\n"
|
||||
"PO-Revision-Date: 2023-02-21 16:14+0100\n"
|
||||
"Last-Translator: S474N <translate@s474n.com>\n"
|
||||
"Language-Team: Czech\n"
|
||||
"Language: cs_CZ\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>=2 && n<=4 ? 1 : 2);\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
|
||||
#: mailu/sso/forms.py:8 mailu/ui/forms.py:79
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: 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 "Heslo"
|
||||
|
||||
#: 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 "Přihlásit se"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8
|
||||
msgid "Admin page for"
|
||||
msgstr "Admin stránka pro"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19
|
||||
msgid "toggle sidebar"
|
||||
msgstr "přepnout postranní panel"
|
||||
|
||||
#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37
|
||||
msgid "change language"
|
||||
msgstr "změnit jazyk"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94
|
||||
msgid "Go to"
|
||||
msgstr "Jít"
|
||||
|
||||
#: 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 "Nastavení klienta"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114
|
||||
msgid "Website"
|
||||
msgstr "Webová stránka"
|
||||
|
||||
#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120
|
||||
msgid "Help"
|
||||
msgstr "Pomoc"
|
||||
|
||||
#: 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 "Registrovat doménu"
|
||||
|
||||
#: 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 "Registrovat se"
|
||||
|
||||
#: mailu/ui/forms.py:33 mailu/ui/forms.py:36
|
||||
msgid "Invalid email address."
|
||||
msgstr "Špatná mailová adresa."
|
||||
|
||||
#: mailu/ui/forms.py:45
|
||||
msgid "Confirm"
|
||||
msgstr "Potvrdit"
|
||||
|
||||
#: 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 "Název domény"
|
||||
|
||||
#: mailu/ui/forms.py:49
|
||||
msgid "Maximum user count"
|
||||
msgstr "Maximální počet uživatelů"
|
||||
|
||||
#: mailu/ui/forms.py:50
|
||||
msgid "Maximum alias count"
|
||||
msgstr "Maximální počet aliasů"
|
||||
|
||||
#: mailu/ui/forms.py:51
|
||||
msgid "Maximum user quota"
|
||||
msgstr "Maximální uživatelská kvóta"
|
||||
|
||||
#: mailu/ui/forms.py:52
|
||||
msgid "Enable sign-up"
|
||||
msgstr "Povolit registraci"
|
||||
|
||||
#: 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 "Komentář"
|
||||
|
||||
#: 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 "Uložit"
|
||||
|
||||
#: mailu/ui/forms.py:59
|
||||
msgid "Initial admin"
|
||||
msgstr "Hlavní admin"
|
||||
|
||||
#: mailu/ui/forms.py:60
|
||||
msgid "Admin password"
|
||||
msgstr "Heslo admina"
|
||||
|
||||
#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94
|
||||
msgid "Confirm password"
|
||||
msgstr "Potvrdit heslo"
|
||||
|
||||
#: mailu/ui/forms.py:63
|
||||
msgid "Create"
|
||||
msgstr "Vytvořit"
|
||||
|
||||
#: mailu/ui/forms.py:67
|
||||
msgid "Alternative name"
|
||||
msgstr "Alternativní jméno"
|
||||
|
||||
#: mailu/ui/forms.py:72
|
||||
msgid "Relayed domain name"
|
||||
msgstr "Seznam předávaných domén"
|
||||
|
||||
#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19
|
||||
msgid "Remote host"
|
||||
msgstr "Vzdálený hostitel"
|
||||
|
||||
#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23
|
||||
#: mailu/ui/templates/user/signup_domain.html:16
|
||||
msgid "Quota"
|
||||
msgstr "Kvóta"
|
||||
|
||||
#: mailu/ui/forms.py:83
|
||||
msgid "Allow IMAP access"
|
||||
msgstr "Povolit přístup IMAP"
|
||||
|
||||
#: mailu/ui/forms.py:84
|
||||
msgid "Allow POP3 access"
|
||||
msgstr "Povolit přístup POP3"
|
||||
|
||||
#: mailu/ui/forms.py:85 mailu/ui/forms.py:101
|
||||
#: mailu/ui/templates/user/settings.html:15
|
||||
msgid "Displayed name"
|
||||
msgstr "Zobrazené jméno"
|
||||
|
||||
#: mailu/ui/forms.py:87
|
||||
msgid "Enabled"
|
||||
msgstr "Povoleno"
|
||||
|
||||
#: mailu/ui/forms.py:92
|
||||
msgid "Email address"
|
||||
msgstr "Emailová adresa"
|
||||
|
||||
#: mailu/ui/forms.py:102
|
||||
msgid "Enable spam filter"
|
||||
msgstr "Povolit filtr spamu"
|
||||
|
||||
#: mailu/ui/forms.py:103
|
||||
msgid "Enable marking spam mails as read"
|
||||
msgstr "Povolit označování spamových e-mailů jako přečtených"
|
||||
|
||||
#: mailu/ui/forms.py:104
|
||||
msgid "Spam filter tolerance"
|
||||
msgstr "Tolerance spamového filtru"
|
||||
|
||||
#: mailu/ui/forms.py:105
|
||||
msgid "Enable forwarding"
|
||||
msgstr "Povolit přeposílání"
|
||||
|
||||
#: mailu/ui/forms.py:106
|
||||
msgid "Keep a copy of the emails"
|
||||
msgstr "Zachovat kopii e-mailů"
|
||||
|
||||
#: mailu/ui/forms.py:107 mailu/ui/forms.py:143
|
||||
#: mailu/ui/templates/alias/list.html:21
|
||||
msgid "Destination"
|
||||
msgstr "Cíl"
|
||||
|
||||
#: mailu/ui/forms.py:108
|
||||
msgid "Save settings"
|
||||
msgstr "Uložit nastavení"
|
||||
|
||||
#: mailu/ui/forms.py:113
|
||||
msgid "Password check"
|
||||
msgstr "Kontrola hesla"
|
||||
|
||||
#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25
|
||||
msgid "Update password"
|
||||
msgstr "Aktualizovat heslo"
|
||||
|
||||
#: mailu/ui/forms.py:118
|
||||
msgid "Enable automatic reply"
|
||||
msgstr "Povolit automatickou odpověď"
|
||||
|
||||
#: mailu/ui/forms.py:119
|
||||
msgid "Reply subject"
|
||||
msgstr "Předmět odpovědi"
|
||||
|
||||
#: mailu/ui/forms.py:120
|
||||
msgid "Reply body"
|
||||
msgstr "Tělo odpovědi"
|
||||
|
||||
#: mailu/ui/forms.py:122
|
||||
msgid "Start of vacation"
|
||||
msgstr "Začátek dovolené"
|
||||
|
||||
#: mailu/ui/forms.py:123
|
||||
msgid "End of vacation"
|
||||
msgstr "Konec dovolené"
|
||||
|
||||
#: mailu/ui/forms.py:124
|
||||
msgid "Update"
|
||||
msgstr "Aktualizovat"
|
||||
|
||||
#: mailu/ui/forms.py:129
|
||||
msgid "Your token (write it down, as it will never be displayed again)"
|
||||
msgstr "Váš token (zapište si ho, protože se již nikdy nezobrazí)"
|
||||
|
||||
#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21
|
||||
msgid "Authorized IP"
|
||||
msgstr "Autorizovaná 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 "Použít syntaxi jako SQL (např. pro doménové koše)"
|
||||
|
||||
#: mailu/ui/forms.py:149
|
||||
msgid "Admin email"
|
||||
msgstr "Email admina"
|
||||
|
||||
#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168
|
||||
msgid "Submit"
|
||||
msgstr "Poslat"
|
||||
|
||||
#: mailu/ui/forms.py:154
|
||||
msgid "Manager email"
|
||||
msgstr "E-mail manažera"
|
||||
|
||||
#: mailu/ui/forms.py:159
|
||||
msgid "Protocol"
|
||||
msgstr "Protokol"
|
||||
|
||||
#: mailu/ui/forms.py:162
|
||||
msgid "Hostname or IP"
|
||||
msgstr "Hostitel nebo IP"
|
||||
|
||||
#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20
|
||||
#: mailu/ui/templates/client.html:45
|
||||
msgid "TCP port"
|
||||
msgstr "TCP port"
|
||||
|
||||
#: mailu/ui/forms.py:164
|
||||
msgid "Enable TLS"
|
||||
msgstr "Povolit 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 "Uživatelské jméno"
|
||||
|
||||
#: mailu/ui/forms.py:167
|
||||
msgid "Keep emails on the server"
|
||||
msgstr "Zachovat e-maily na serveru"
|
||||
|
||||
#: mailu/ui/forms.py:172
|
||||
msgid "Announcement subject"
|
||||
msgstr "Předmět oznámení"
|
||||
|
||||
#: mailu/ui/forms.py:174
|
||||
msgid "Announcement body"
|
||||
msgstr "Tělo oznámení"
|
||||
|
||||
#: mailu/ui/forms.py:176
|
||||
msgid "Send"
|
||||
msgstr "Poslat"
|
||||
|
||||
#: mailu/ui/templates/announcement.html:4
|
||||
msgid "Public announcement"
|
||||
msgstr "Veřejné oznámení"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80
|
||||
#: mailu/ui/templates/user/settings.html:19
|
||||
msgid "Antispam"
|
||||
msgstr "Antispam"
|
||||
|
||||
#: mailu/ui/templates/antispam.html:8
|
||||
msgid "RSPAMD status page"
|
||||
msgstr "Stavová stránka RSPAMD"
|
||||
|
||||
#: mailu/ui/templates/client.html:8
|
||||
msgid "configure your email client"
|
||||
msgstr "nakonfigurovat e-mailového klienta"
|
||||
|
||||
#: mailu/ui/templates/client.html:13
|
||||
msgid "Incoming mail"
|
||||
msgstr "Příchozí mail"
|
||||
|
||||
#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41
|
||||
msgid "Mail protocol"
|
||||
msgstr "Poštovní protokol"
|
||||
|
||||
#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49
|
||||
msgid "Server name"
|
||||
msgstr "Název serveru"
|
||||
|
||||
#: mailu/ui/templates/client.html:38
|
||||
msgid "Outgoing mail"
|
||||
msgstr "Odchozí pošta"
|
||||
|
||||
#: mailu/ui/templates/confirm.html:4
|
||||
msgid "Confirm action"
|
||||
msgstr "Potvrdit akci"
|
||||
|
||||
#: mailu/ui/templates/confirm.html:13
|
||||
#, python-format
|
||||
msgid "You are about to %(action)s. Please confirm your action."
|
||||
msgstr "Chystáte se %(action)s. Potvrďte prosím vaši akci."
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:4
|
||||
msgid "Docker error"
|
||||
msgstr "Chyba Dockeru"
|
||||
|
||||
#: mailu/ui/templates/docker-error.html:12
|
||||
msgid "An error occurred while talking to the Docker server."
|
||||
msgstr "Při komunikaci se serverem Docker došlo k chybě."
|
||||
|
||||
#: mailu/ui/templates/macros.html:129
|
||||
msgid "copy to clipboard"
|
||||
msgstr "zkopírovat do schránky"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:15
|
||||
msgid "My account"
|
||||
msgstr "Můj účet"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37
|
||||
msgid "Settings"
|
||||
msgstr "Nastavení"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38
|
||||
msgid "Auto-reply"
|
||||
msgstr "Automatická odpověď"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37
|
||||
#: mailu/ui/templates/user/list.html:39
|
||||
msgid "Fetched accounts"
|
||||
msgstr "Fetched účty"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4
|
||||
msgid "Authentication tokens"
|
||||
msgstr "Autentizační tokeny"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:56
|
||||
msgid "Administration"
|
||||
msgstr "Administrace"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:62
|
||||
msgid "Announcement"
|
||||
msgstr "Oznámení"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:68
|
||||
msgid "Administrators"
|
||||
msgstr "Administrátoři"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:74
|
||||
msgid "Relayed domains"
|
||||
msgstr "Relayované domény"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:88
|
||||
msgid "Mail domains"
|
||||
msgstr "Poštovní domény"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:99
|
||||
msgid "Webmail"
|
||||
msgstr "Webmail"
|
||||
|
||||
#: mailu/ui/templates/sidebar.html:135
|
||||
msgid "Sign out"
|
||||
msgstr "Odhlásit se"
|
||||
|
||||
#: mailu/ui/templates/working.html:4
|
||||
msgid "We are still working on this feature!"
|
||||
msgstr "Na této funkci stále pracujeme!"
|
||||
|
||||
#: mailu/ui/templates/admin/create.html:4
|
||||
msgid "Add a global administrator"
|
||||
msgstr "Přidat globálního administrátora"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:4
|
||||
msgid "Global administrators"
|
||||
msgstr "Globální administrátor"
|
||||
|
||||
#: mailu/ui/templates/admin/list.html:9
|
||||
msgid "Add administrator"
|
||||
msgstr "Přidat administrátora"
|
||||
|
||||
#: 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 "Akce"
|
||||
|
||||
#: 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 "Email"
|
||||
|
||||
#: 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 "Vymazat"
|
||||
|
||||
#: mailu/ui/templates/alias/create.html:4
|
||||
msgid "Create alias"
|
||||
msgstr "Vytvořit alias"
|
||||
|
||||
#: mailu/ui/templates/alias/edit.html:4
|
||||
msgid "Edit alias"
|
||||
msgstr "Upravit alias"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:4
|
||||
msgid "Alias list"
|
||||
msgstr "Seznam aliasů"
|
||||
|
||||
#: mailu/ui/templates/alias/list.html:12
|
||||
msgid "Add alias"
|
||||
msgstr "Přidat alias"
|
||||
|
||||
#: 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 "Vytvořeno"
|
||||
|
||||
#: 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 "Poslední úprava"
|
||||
|
||||
#: 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 "Upravit"
|
||||
|
||||
#: mailu/ui/templates/alternative/create.html:4
|
||||
msgid "Create alternative domain"
|
||||
msgstr "Vytvořit alternativní doménu"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:4
|
||||
msgid "Alternative domain list"
|
||||
msgstr "Seznam alternativních domén"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:12
|
||||
msgid "Add alternative"
|
||||
msgstr "Přidat alternativu"
|
||||
|
||||
#: mailu/ui/templates/alternative/list.html:20
|
||||
msgid "Name"
|
||||
msgstr "Jméno"
|
||||
|
||||
#: mailu/ui/templates/domain/create.html:4
|
||||
#: mailu/ui/templates/domain/list.html:9
|
||||
msgid "New domain"
|
||||
msgstr "Nová doména"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:4
|
||||
msgid "Domain details"
|
||||
msgstr "Podrobnosti o doméně"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:15
|
||||
msgid "Regenerate keys"
|
||||
msgstr "Obnovit klíče"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:17
|
||||
msgid "Generate keys"
|
||||
msgstr "Generovat klíče"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:30
|
||||
msgid "DNS MX entry"
|
||||
msgstr "Záznam DNS MX"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:34
|
||||
msgid "DNS SPF entries"
|
||||
msgstr "Záznamy DNS SPF"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:40
|
||||
msgid "DKIM public key"
|
||||
msgstr "Veřejný klíč DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:44
|
||||
msgid "DNS DKIM entry"
|
||||
msgstr "Záznam DNS DKIM"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:48
|
||||
msgid "DNS DMARC entry"
|
||||
msgstr "Záznam DNS DMARC"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:58
|
||||
msgid "DNS TLSA entry"
|
||||
msgstr "Záznam DNS TLSA"
|
||||
|
||||
#: mailu/ui/templates/domain/details.html:63
|
||||
msgid "DNS client auto-configuration entries"
|
||||
msgstr "Položky automatické konfigurace klienta DNS"
|
||||
|
||||
#: mailu/ui/templates/domain/edit.html:4
|
||||
msgid "Edit domain"
|
||||
msgstr "Upravit doménu"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:4
|
||||
msgid "Domain list"
|
||||
msgstr "Seznam domén"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:18
|
||||
msgid "Manage"
|
||||
msgstr "Spravovat"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:20
|
||||
msgid "Mailbox count"
|
||||
msgstr "Počet poštovních schránek"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:21
|
||||
msgid "Alias count"
|
||||
msgstr "Počet aliasů"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:31
|
||||
msgid "Details"
|
||||
msgstr "Podrobnosti"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:38
|
||||
msgid "Users"
|
||||
msgstr "Uživatelů"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:39
|
||||
msgid "Aliases"
|
||||
msgstr "Aliasů"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:40
|
||||
msgid "Managers"
|
||||
msgstr "Manažerů"
|
||||
|
||||
#: mailu/ui/templates/domain/list.html:42
|
||||
msgid "Alternatives"
|
||||
msgstr "Alternativ"
|
||||
|
||||
#: 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 ""
|
||||
"Chcete-li zaregistrovat novou doménu, musíte nejprve nastavit\n"
|
||||
" zónu domény tak, aby doménový <code>MX</code> záznam ukazovala na tento "
|
||||
"server"
|
||||
|
||||
#: 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 ""
|
||||
"Pokud nevíte, jak nastavit <code>MX</code> záznam pro zónu DNS,\n"
|
||||
" kontaktujte svého poskytovatele DNS nebo správce. Také prosím počkejte "
|
||||
"a\n"
|
||||
" několik minut po <code>MX</code> tak, aby vypršela v mezipaměti "
|
||||
"místního\n"
|
||||
" serveru."
|
||||
|
||||
#: mailu/ui/templates/fetch/create.html:4
|
||||
msgid "Add a fetched account"
|
||||
msgstr "Přidejte fetched účet"
|
||||
|
||||
#: mailu/ui/templates/fetch/edit.html:4
|
||||
msgid "Update a fetched account"
|
||||
msgstr "Aktualizujte fetched účet"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:12
|
||||
msgid "Add an account"
|
||||
msgstr "Přidat účet"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:20
|
||||
msgid "Endpoint"
|
||||
msgstr "Koncový bod"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:22
|
||||
msgid "Keep emails"
|
||||
msgstr "Zachovat emaily"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:23
|
||||
msgid "Last check"
|
||||
msgstr "Poslední kontrola"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:24
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
msgid "yes"
|
||||
msgstr "ano"
|
||||
|
||||
#: mailu/ui/templates/fetch/list.html:38
|
||||
msgid "no"
|
||||
msgstr "ne"
|
||||
|
||||
#: mailu/ui/templates/manager/create.html:4
|
||||
msgid "Add a manager"
|
||||
msgstr "Přidat manažera"
|
||||
|
||||
#: mailu/ui/templates/manager/list.html:4
|
||||
msgid "Manager list"
|
||||
msgstr "Seznam manažerů"
|
||||
|
||||
#: mailu/ui/templates/manager/list.html:12
|
||||
msgid "Add manager"
|
||||
msgstr "Přidat manažera"
|
||||
|
||||
#: mailu/ui/templates/relay/create.html:4
|
||||
msgid "New relay domain"
|
||||
msgstr "Nová relay doména"
|
||||
|
||||
#: mailu/ui/templates/relay/edit.html:4
|
||||
msgid "Edit relayed domain"
|
||||
msgstr "Upravit relay doménu"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:4
|
||||
msgid "Relayed domain list"
|
||||
msgstr "Seznam relay domén"
|
||||
|
||||
#: mailu/ui/templates/relay/list.html:9
|
||||
msgid "New relayed domain"
|
||||
msgstr "Nová relay doména"
|
||||
|
||||
#: mailu/ui/templates/token/create.html:4
|
||||
msgid "Create an authentication token"
|
||||
msgstr "Vytvořit ověřovací token"
|
||||
|
||||
#: mailu/ui/templates/token/list.html:12
|
||||
msgid "New token"
|
||||
msgstr "Nový token"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:4
|
||||
msgid "New user"
|
||||
msgstr "Nový uživatel"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:15
|
||||
msgid "General"
|
||||
msgstr "Všeobecné"
|
||||
|
||||
#: mailu/ui/templates/user/create.html:23
|
||||
msgid "Features and quotas"
|
||||
msgstr "Funkce a kvóty"
|
||||
|
||||
#: mailu/ui/templates/user/edit.html:4
|
||||
msgid "Edit user"
|
||||
msgstr "Upravit uživatele"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:4
|
||||
msgid "User list"
|
||||
msgstr "Seznam uživatelů"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:12
|
||||
msgid "Add user"
|
||||
msgstr "Přidat uživatele"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:20 mailu/ui/templates/user/settings.html:4
|
||||
msgid "User settings"
|
||||
msgstr "Uživatelské nastavení"
|
||||
|
||||
#: mailu/ui/templates/user/list.html:22
|
||||
msgid "Features"
|
||||
msgstr "Funkce"
|
||||
|
||||
#: mailu/ui/templates/user/password.html:4
|
||||
msgid "Password update"
|
||||
msgstr "Aktualizace hesla"
|
||||
|
||||
#: mailu/ui/templates/user/reply.html:4
|
||||
msgid "Automatic reply"
|
||||
msgstr "Automatická odpověď"
|
||||
|
||||
#: mailu/ui/templates/user/settings.html:27
|
||||
msgid "Auto-forward"
|
||||
msgstr "Automatické přeposlání"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:8
|
||||
msgid "pick a domain for the new account"
|
||||
msgstr "vybrat doménu pro nový účet"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:14
|
||||
msgid "Domain"
|
||||
msgstr "Doména"
|
||||
|
||||
#: mailu/ui/templates/user/signup_domain.html:15
|
||||
msgid "Available slots"
|
||||
msgstr "Dostupných slotů"
|
@ -1,3 +1,3 @@
|
||||
pip==22.3
|
||||
setuptools==65.5.0
|
||||
wheel==0.37.1
|
||||
pip==22.3.1
|
||||
setuptools==65.6.3
|
||||
wheel==0.38.4
|
||||
|
@ -0,0 +1,31 @@
|
||||
# syntax=docker/dockerfile-upstream:1.4.3
|
||||
|
||||
# oletools image
|
||||
FROM base
|
||||
|
||||
ARG VERSION=local
|
||||
LABEL version=$VERSION
|
||||
|
||||
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 \
|
||||
; chmod 755 olefy.py
|
||||
|
||||
RUN echo $VERSION >/version
|
||||
|
||||
HEALTHCHECK --start-period=60s CMD echo PING|nc -q1 127.0.0.1 11343|grep "PONG"
|
||||
#EXPOSE 11343/tcp
|
||||
|
||||
USER nobody:nobody
|
||||
|
||||
ENV \
|
||||
OLEFY_BINDADDRESS="" \
|
||||
OLEFY_BINDPORT="11343" \
|
||||
OLEFY_OLEVBA_PATH="/app/venv/bin/olevba" \
|
||||
OLEFY_PYTHON_PATH="/app/venv/bin/python3" \
|
||||
OLEFY_TMPDIR="/dev/shm/" \
|
||||
OLEFY_MINLENGTH="300" \
|
||||
OLEFY_DEL_TMP="1" \
|
||||
OLEFY_DEL_TMP_FAILED="1"
|
||||
|
||||
CMD /app/olefy.py
|
@ -1,40 +1,43 @@
|
||||
# rsyslog configuration file
|
||||
#
|
||||
# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
|
||||
# or latest version online at http://www.rsyslog.com/doc/rsyslog_conf.html
|
||||
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html
|
||||
|
||||
|
||||
#### Global directives ####
|
||||
|
||||
# Sets the directory that rsyslog uses for work files.
|
||||
$WorkDirectory /var/lib/rsyslog
|
||||
|
||||
# Sets default permissions for all log files.
|
||||
$FileOwner root
|
||||
$FileGroup adm
|
||||
$FileCreateMode 0640
|
||||
$DirCreateMode 0755
|
||||
$Umask 0022
|
||||
|
||||
# Reduce repeating messages (default off).
|
||||
$RepeatedMsgReduction on
|
||||
|
||||
|
||||
#### Modules ####
|
||||
|
||||
# Provides support for local system logging (e.g. via logger command).
|
||||
module(load="imuxsock")
|
||||
|
||||
#### Rules ####
|
||||
|
||||
# Discard messages from local test requests
|
||||
:msg, contains, "connect from localhost[127.0.0.1]" ~
|
||||
|
||||
{% if POSTFIX_LOG_FILE %}
|
||||
# Log mail logs to file
|
||||
mail.* -{{POSTFIX_LOG_FILE}}
|
||||
{% endif %}
|
||||
|
||||
# Log mail logs to stdout
|
||||
mail.* -/dev/stdout
|
||||
# rsyslog configuration file
|
||||
#
|
||||
# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
|
||||
# or latest version online at http://www.rsyslog.com/doc/rsyslog_conf.html
|
||||
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html
|
||||
|
||||
|
||||
#### Global directives ####
|
||||
|
||||
# Sets the directory that rsyslog uses for work files.
|
||||
$WorkDirectory /var/lib/rsyslog
|
||||
|
||||
# Sets default permissions for all log files.
|
||||
$FileOwner root
|
||||
$FileGroup adm
|
||||
$FileCreateMode 0640
|
||||
$DirCreateMode 0755
|
||||
$Umask 0022
|
||||
|
||||
# Reduce repeating messages (default off).
|
||||
$RepeatedMsgReduction on
|
||||
|
||||
|
||||
#### Modules ####
|
||||
|
||||
# Provides support for local system logging (e.g. via logger command).
|
||||
module(load="imuxsock")
|
||||
|
||||
#### Rules ####
|
||||
|
||||
# Discard messages from local test requests
|
||||
:msg, contains, "connect from localhost[127.0.0.1]" ~
|
||||
:msg, contains, "connect from localhost[::1]" ~
|
||||
:msg, contains, "haproxy read: short protocol header: QUIT" ~
|
||||
:msg, contains, "discarding EHLO keywords: PIPELINING" ~
|
||||
|
||||
{% if POSTFIX_LOG_FILE %}
|
||||
# Log mail logs to file
|
||||
mail.* -{{POSTFIX_LOG_FILE}}
|
||||
{% endif %}
|
||||
|
||||
# Log mail logs to stdout
|
||||
mail.* -/dev/stdout
|
||||
|
@ -0,0 +1,14 @@
|
||||
{% if SCAN_MACROS == 'True' %}
|
||||
OLETOOLS_MACRO_MRAPTOR {
|
||||
expression = "(OLETOOLS_A & OLETOOLS_W) | (OLETOOLS_A & OLETOOLS_X) | (OLETOOLS_W & OLETOOLS_X)";
|
||||
message = "Rejected (malicious macro - mraptor)";
|
||||
policy = "leave";
|
||||
score = 20.0;
|
||||
}
|
||||
OLETOOLS_MACRO_SUSPICIOUS {
|
||||
expression = "OLETOOLS_FLAG | OLETOOLS_VBASTOMP | OLETOOLS_A";
|
||||
message = "Rejected (malicious macro)";
|
||||
policy = "leave";
|
||||
score = 20.0;
|
||||
}
|
||||
{% endif %}
|
@ -0,0 +1,64 @@
|
||||
{% if SCAN_MACROS == 'True' %}
|
||||
oletools {
|
||||
# default olefy settings
|
||||
servers = "{{ OLETOOLS_ADDRESS }}:11343"
|
||||
|
||||
# needs to be set explicitly for Rspamd < 1.9.5
|
||||
scan_mime_parts = true;
|
||||
extended = true;
|
||||
max_size = 3145728;
|
||||
timeout = 20.0;
|
||||
retransmits = 1;
|
||||
|
||||
patterns {
|
||||
OLETOOLS_MACRO_FOUND= '^.....M..$';
|
||||
OLETOOLS_AUTOEXEC = '^A....M..$';
|
||||
OLETOOLS_FLAG = '^.....MS.$';
|
||||
OLETOOLS_VBASTOMP = '^VBA Stomping$';
|
||||
# see https://github.com/decalage2/oletools/blob/master/oletools/mraptor.py
|
||||
OLETOOLS_A = '(?i)\b(?:Auto(?:Exec|_?Open|_?Close|Exit|New)|Document(?:_?Open|_Close|_?BeforeClose|Change|_New)|NewDocument|Workbook(?:_Open|_Activate|_Close|_BeforeClose)|\w+_(?:Painted|Painting|GotFocus|LostFocus|MouseHover|Layout|Click|Change|Resize|BeforeNavigate2|BeforeScriptExecute|DocumentComplete|DownloadBegin|DownloadComplete|FileDownload|NavigateComplete2|NavigateError|ProgressChange|PropertyChange|SetSecureLockIcon|StatusTextChange|TitleChange|MouseMove|MouseEnter|MouseLeave|OnConnecting))\b|Auto_Ope\b';
|
||||
OLETOOLS_W = '(?i)\b(?:FileCopy|CopyFile|Kill|CreateTextFile|VirtualAlloc|RtlMoveMemory|URLDownloadToFileA?|AltStartupPath|WriteProcessMemory|ADODB\.Stream|WriteText|SaveToFile|SaveAs|SaveAsRTF|FileSaveAs|MkDir|RmDir|SaveSetting|SetAttr)\b|(?:\bOpen\b[^\n]+\b(?:Write|Append|Binary|Output|Random)\b)';
|
||||
OLETOOLS_X = '(?i)\b(?:Shell|CreateObject|GetObject|SendKeys|RUN|CALL|MacScript|FollowHyperlink|CreateThread|ShellExecuteA?|ExecuteExcel4Macro|EXEC|REGISTER|SetTimer)\b|(?:\bDeclare\b[^\n]+\bLib\b)';
|
||||
}
|
||||
|
||||
# mime-part regex matching in content-type or filename
|
||||
mime_parts_filter_regex {
|
||||
#UNKNOWN = "application\/octet-stream";
|
||||
DOC2 = "application\/msword";
|
||||
DOC3 = "application\/vnd\.ms-word.*";
|
||||
XLS = "application\/vnd\.ms-excel.*";
|
||||
PPT = "application\/vnd\.ms-powerpoint.*";
|
||||
GENERIC = "application\/vnd\.openxmlformats-officedocument.*";
|
||||
}
|
||||
# mime-part filename extension matching (no regex)
|
||||
mime_parts_filter_ext {
|
||||
doc = "doc";
|
||||
dot = "dot";
|
||||
docx = "docx";
|
||||
dotx = "dotx";
|
||||
docm = "docm";
|
||||
dotm = "dotm";
|
||||
xls = "xls";
|
||||
xlt = "xlt";
|
||||
xla = "xla";
|
||||
xlsx = "xlsx";
|
||||
xltx = "xltx";
|
||||
xlsm = "xlsm";
|
||||
xltm = "xltm";
|
||||
xlam = "xlam";
|
||||
xlsb = "xlsb";
|
||||
ppt = "ppt";
|
||||
pot = "pot";
|
||||
pps = "pps";
|
||||
ppa = "ppa";
|
||||
pptx = "pptx";
|
||||
potx = "potx";
|
||||
ppsx = "ppsx";
|
||||
ppam = "ppam";
|
||||
pptm = "pptm";
|
||||
potm = "potm";
|
||||
ppsm = "ppsm";
|
||||
slk = "slk";
|
||||
}
|
||||
}
|
||||
{% endif %}
|
@ -0,0 +1,40 @@
|
||||
{% if SCAN_MACROS == 'True' %}
|
||||
# local.d/external_services_group.conf
|
||||
|
||||
description = "Oletools content rules";
|
||||
symbols = {
|
||||
"OLETOOLS" {
|
||||
weight = 1.0;
|
||||
description = "OLETOOLS found a Macro";
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_MACRO_FOUND" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_AUTOEXEC" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_FLAG" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_VBASTOMP" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_A" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_W" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
"OLETOOLS_X" {
|
||||
weight = 0.0;
|
||||
one_shot = true;
|
||||
},
|
||||
}
|
||||
{% endif %}
|
@ -0,0 +1,68 @@
|
||||
ace
|
||||
ade
|
||||
adp
|
||||
apk
|
||||
appx
|
||||
appxbundle
|
||||
arj
|
||||
bat
|
||||
bin
|
||||
cab
|
||||
chm
|
||||
class
|
||||
cmd
|
||||
com
|
||||
cpl
|
||||
diagcab
|
||||
diagcfg
|
||||
diagpack
|
||||
dll
|
||||
ex
|
||||
ex_
|
||||
exe
|
||||
hlp
|
||||
hta
|
||||
img
|
||||
ins
|
||||
iso
|
||||
isp
|
||||
jar
|
||||
jnlp
|
||||
js
|
||||
jse
|
||||
lib
|
||||
lnk
|
||||
lzh
|
||||
mde
|
||||
msc
|
||||
msi
|
||||
msix
|
||||
msixbundle
|
||||
msp
|
||||
mst
|
||||
msu
|
||||
nsh
|
||||
ocx
|
||||
ovl
|
||||
pif
|
||||
ps1
|
||||
r01
|
||||
r14
|
||||
r18
|
||||
r25
|
||||
scr
|
||||
sct
|
||||
shb
|
||||
shs
|
||||
sys
|
||||
vb
|
||||
vbe
|
||||
vbs
|
||||
vbscript
|
||||
vdl
|
||||
vhd
|
||||
vxd
|
||||
wsc
|
||||
wsf
|
||||
wsh
|
||||
xll
|
@ -1,17 +1,17 @@
|
||||
rules {
|
||||
ANTISPOOF_NOAUTH {
|
||||
action = "reject";
|
||||
expression = "!MAILLIST & ((IS_LOCAL_DOMAIN_E & MISSING_FROM) | (IS_LOCAL_DOMAIN_H & (R_DKIM_NA & R_SPF_NA & DMARC_NA & ARC_NA)))";
|
||||
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & ((IS_LOCAL_DOMAIN_E & MISSING_FROM) | (IS_LOCAL_DOMAIN_H & (R_DKIM_NA & R_SPF_NA & DMARC_NA & ARC_NA)))";
|
||||
message = "Rejected (anti-spoofing: noauth). Please setup DMARC with DKIM or SPF if you want to send emails from your domain from other servers.";
|
||||
}
|
||||
ANTISPOOF_DMARC_ENFORCE_LOCAL {
|
||||
action = "reject";
|
||||
expression = "!MAILLIST & (IS_LOCAL_DOMAIN_H | IS_LOCAL_DOMAIN_E) & (DMARC_POLICY_SOFTFAIL | DMARC_POLICY_REJECT | DMARC_POLICY_QUARANTINE | DMARC_NA)";
|
||||
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & (IS_LOCAL_DOMAIN_H | IS_LOCAL_DOMAIN_E) & (DMARC_POLICY_SOFTFAIL | DMARC_POLICY_REJECT | DMARC_POLICY_QUARANTINE | DMARC_NA)";
|
||||
message = "Rejected (anti-spoofing: DMARC compliance is enforced for local domains, regardless of the policy setting)";
|
||||
}
|
||||
ANTISPOOF_AUTH_FAILED {
|
||||
action = "reject";
|
||||
expression = "!MAILLIST & BLACKLIST_ANTISPOOF";
|
||||
expression = "!IS_LOCALLY_GENERATED & !MAILLIST & BLACKLIST_ANTISPOOF";
|
||||
message = "Rejected (anti-spoofing: auth-failed)";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,2 @@
|
||||
{{ SUBNET }}
|
||||
{{ SUBNET6 }}
|
@ -1,3 +1 @@
|
||||
{% if RELAYNETS %}
|
||||
local_networks = [{{ RELAYNETS }}];
|
||||
{% endif %}
|
||||
local_networks = [{{ SUBNET }}{% if SUBNET6 %}, {{ SUBNET6 }}{% endif %}{% if RELAYNETS %}, {{ RELAYNETS }}{% endif %}];
|
||||
|
@ -0,0 +1,33 @@
|
||||
Mailu RESTful API
|
||||
=================
|
||||
|
||||
Mailu offers a RESTful API for changing the Mailu configuration.
|
||||
Anything that can be configured via the Mailu web administration interface,
|
||||
can also be configured via the API.
|
||||
|
||||
The Mailu API can be configured via the setup utility (setup.mailu.io).
|
||||
It can also be manually configured via mailu.env:
|
||||
|
||||
* ``API`` - Expose the API interface (value: true, false)
|
||||
* ``WEB_API`` - Path to the API interface
|
||||
* ``API_TOKEN`` - API token for authentication
|
||||
|
||||
For more information refer to the detailed descriptions in the
|
||||
:ref:`configuration reference <advanced_settings>`.
|
||||
|
||||
|
||||
Swagger.json
|
||||
------------
|
||||
|
||||
The swagger.json file can be retrieved via: https://myserver/api/v1/swagger.json
|
||||
(WEB_API=/api)
|
||||
The swagger.json file can be consumed in programs such as Postman for generating all API calls.
|
||||
|
||||
|
||||
In-built SwaggerUI
|
||||
------------------
|
||||
The Mailu API comes with an in-built SwaggerUI. It is a web client that allows
|
||||
anyone to visualize and interact with the Mailu API.
|
||||
|
||||
Assuming ``/api`` is configured as value for ``WEB_API``, it
|
||||
is accessible via the URL: https://myserver/api/
|
@ -1,5 +1,5 @@
|
||||
recommonmark
|
||||
Sphinx
|
||||
sphinx-autobuild
|
||||
sphinx-rtd-theme
|
||||
recommonmark==0.7.1
|
||||
Sphinx==5.2.0
|
||||
sphinx-autobuild==2021.3.14
|
||||
sphinx-rtd-theme==1.0.0
|
||||
docutils==0.16
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue