Merge branch 'master' into feature-traefik
This commit is contained in:
@@ -55,6 +55,9 @@ EXPOSE_ADMIN=no
|
|||||||
# Dav server implementation (value: radicale, none)
|
# Dav server implementation (value: radicale, none)
|
||||||
WEBDAV=none
|
WEBDAV=none
|
||||||
|
|
||||||
|
# Antivirus solution (value: none, clamav)
|
||||||
|
ANTIVIRUS=clamav
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
# Mail settings
|
# Mail settings
|
||||||
###################################
|
###################################
|
||||||
@@ -77,6 +80,10 @@ FETCHMAIL_DELAY=600
|
|||||||
# e.g. localpart+custom@domain;tld
|
# e.g. localpart+custom@domain;tld
|
||||||
RECIPIENT_DELIMITER=+
|
RECIPIENT_DELIMITER=+
|
||||||
|
|
||||||
|
# DMARC rua and ruf email
|
||||||
|
DMARC_RUA=admin
|
||||||
|
DMARC_RUF=admin
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
# Nginx settings
|
# Nginx settings
|
||||||
###################################
|
###################################
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ default_config = {
|
|||||||
'BOOTSTRAP_SERVE_LOCAL': True,
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
||||||
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
||||||
'DKIM_SELECTOR': 'dkim',
|
'DKIM_SELECTOR': 'dkim',
|
||||||
|
'DMARC_RUA': None,
|
||||||
|
'DMARC_RUF': None,
|
||||||
'BABEL_DEFAULT_LOCALE': 'en',
|
'BABEL_DEFAULT_LOCALE': 'en',
|
||||||
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
||||||
'FRONTEND': 'none',
|
'FRONTEND': 'none',
|
||||||
|
|||||||
@@ -28,4 +28,6 @@ from mailu.admin.views import \
|
|||||||
aliases, \
|
aliases, \
|
||||||
users, \
|
users, \
|
||||||
domains, \
|
domains, \
|
||||||
|
relays, \
|
||||||
|
alternatives, \
|
||||||
fetches
|
fetches
|
||||||
|
|||||||
@@ -51,6 +51,18 @@ class DomainForm(flask_wtf.FlaskForm):
|
|||||||
submit = fields.SubmitField(_('Create'))
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
|
class AlternativeForm(flask_wtf.FlaskForm):
|
||||||
|
name = fields.StringField(_('Alternative name'), [validators.DataRequired()])
|
||||||
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
|
class RelayForm(flask_wtf.FlaskForm):
|
||||||
|
name = fields.StringField(_('Relayed domain name'), [validators.DataRequired()])
|
||||||
|
smtp = fields.StringField(_('Remote host'))
|
||||||
|
comment = fields.StringField(_('Comment'))
|
||||||
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
class UserForm(flask_wtf.FlaskForm):
|
class UserForm(flask_wtf.FlaskForm):
|
||||||
localpart = fields.StringField(_('E-mail'), [validators.DataRequired()])
|
localpart = fields.StringField(_('E-mail'), [validators.DataRequired()])
|
||||||
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
||||||
|
|||||||
@@ -100,6 +100,36 @@ class Domain(Base):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Alternative(Base):
|
||||||
|
""" Alternative name for a served domain.
|
||||||
|
The name "domain alias" was avoided to prevent some confusion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "alternative"
|
||||||
|
|
||||||
|
name = db.Column(db.String(80), primary_key=True, nullable=False)
|
||||||
|
domain_name = db.Column(db.String(80), db.ForeignKey(Domain.name))
|
||||||
|
domain = db.relationship(Domain,
|
||||||
|
backref=db.backref('alternatives', cascade='all, delete-orphan'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Relay(Base):
|
||||||
|
""" Relayed mail domain.
|
||||||
|
The domain is either relayed publicly or through a specified SMTP host.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "relay"
|
||||||
|
|
||||||
|
name = db.Column(db.String(80), primary_key=True, nullable=False)
|
||||||
|
smtp = db.Column(db.String(80), nullable=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Email(object):
|
class Email(object):
|
||||||
""" Abstraction for an email address (localpart and domain).
|
""" Abstraction for an email address (localpart and domain).
|
||||||
"""
|
"""
|
||||||
|
|||||||
9
admin/mailu/admin/templates/alternative/create.html
Normal file
9
admin/mailu/admin/templates/alternative/create.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "form.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Create alternative domain{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain }}
|
||||||
|
{% endblock %}
|
||||||
34
admin/mailu/admin/templates/alternative/list.html
Normal file
34
admin/mailu/admin/templates/alternative/list.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Alternative domain list{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_action %}
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('.alternative_create', domain_name=domain.name) }}">{% trans %}Add alternative{% endtrans %}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Name{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Created{% endtrans %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for alternative in domain.alternatives %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('.alternative_delete', alternative=alternative.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ alternative }}</td>
|
||||||
|
<td>{{ alternative.created_at }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans %}DNS DMARC entry{% endtrans %}</th>
|
<th>{% trans %}DNS DMARC entry{% endtrans %}</th>
|
||||||
<td><pre>_dmarc.{{ domain.name }}. 600 IN TXT "v=DMARC1; p=reject; rua=mailto:{{ config["POSTMASTER"] }}@{{ config["DOMAIN"] }}; adkim=s; aspf=s"</pre></td>
|
<td><pre>_dmarc.{{ domain.name }}. 600 IN TXT "v=DMARC1; p=reject;{% if config["DMARC_RUA"] %} rua=mailto:{{ config["DMARC_RUA"] }}@{{ config["DOMAIN"] }};{% endif %}{% if config["DMARC_RUF"] %} ruf=mailto:{{ config["DMARC_RUF"] }}@{{ config["DOMAIN"] }};{% endif %} adkim=s; aspf=s"</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="{% trans %}Users{% endtrans %}"><i class="fa fa-envelope-o"></i></a>
|
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="{% trans %}Users{% endtrans %}"><i class="fa fa-envelope-o"></i></a>
|
||||||
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>
|
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>
|
||||||
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>
|
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>
|
||||||
|
{% if current_user.global_admin %}
|
||||||
|
<a href="{{ url_for('.alternative_list', domain_name=domain.name) }}" title="{% trans %}Alternatives{% endtrans %}"><i class="fa fa-asterisk"></i></a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ domain.name }}</td>
|
<td>{{ domain.name }}</td>
|
||||||
<td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td>
|
<td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td>
|
||||||
|
|||||||
5
admin/mailu/admin/templates/relay/create.html
Normal file
5
admin/mailu/admin/templates/relay/create.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends "form.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}New relay domain{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
9
admin/mailu/admin/templates/relay/edit.html
Normal file
9
admin/mailu/admin/templates/relay/edit.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "form.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Edit relayd domain{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ relay }}
|
||||||
|
{% endblock %}
|
||||||
39
admin/mailu/admin/templates/relay/list.html
Normal file
39
admin/mailu/admin/templates/relay/list.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Relayed domain list{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_action %}
|
||||||
|
{% if current_user.global_admin %}
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('.relay_create') }}">{% trans %}New relayed domain{% endtrans %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Domain name{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Remote host{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Comment{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Created{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Last edit{% endtrans %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for relay in relays %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('.relay_edit', relay_name=relay.name) }}" title="{% trans %}Edit{% endtrans %}"><i class="fa fa-pencil"></i></a>
|
||||||
|
<a href="{{ url_for('.relay_delete', relay_name=relay.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ relay.name }}</td>
|
||||||
|
<td>{{ relay.smtp or '-' }}</td>
|
||||||
|
<td>{{ relay.comment or '' }}</td>
|
||||||
|
<td>{{ relay.created_at }}</td>
|
||||||
|
<td>{{ relay.updated_at or '' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
@@ -50,6 +50,11 @@
|
|||||||
<i class="fa fa-user"></i> <span>{% trans %}Administrators{% endtrans %}</span>
|
<i class="fa fa-user"></i> <span>{% trans %}Administrators{% endtrans %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('.relay_list') }}">
|
||||||
|
<i class="fa fa-reply-all"></i> <span>{% trans %}Relayed domains{% endtrans %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if current_user.manager_of or current_user.global_admin %}
|
{% if current_user.manager_of or current_user.global_admin %}
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
47
admin/mailu/admin/views/alternatives.py
Normal file
47
admin/mailu/admin/views/alternatives.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from mailu.admin import app, db, models, forms, access
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import wtforms_components
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/alternative/list/<domain_name>', methods=['GET'])
|
||||||
|
@access.global_admin
|
||||||
|
def alternative_list(domain_name):
|
||||||
|
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||||
|
return flask.render_template('alternative/list.html', domain=domain)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/alternative/create/<domain_name>', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
def alternative_create(domain_name):
|
||||||
|
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||||
|
form = forms.AlternativeForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||||
|
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||||
|
conflicting_relay = models.Relay.query.get(form.name.data)
|
||||||
|
if conflicting_domain or conflicting_alternative or conflicting_relay:
|
||||||
|
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
||||||
|
else:
|
||||||
|
alternative = models.Alternative(domain=domain)
|
||||||
|
form.populate_obj(alternative)
|
||||||
|
db.session.add(alternative)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Alternative domain %s created' % alternative)
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.alternative_list', domain_name=domain.name))
|
||||||
|
return flask.render_template('alternative/create.html',
|
||||||
|
domain=domain, form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/alternative/delete/<alternative>', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
@access.confirmation_required("delete {alternative}")
|
||||||
|
def alternative_delete(alternative):
|
||||||
|
alternative = models.Alternative.query.get(alternative) or flask.abort(404)
|
||||||
|
domain = alternative.domain
|
||||||
|
db.session.delete(alternative)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Alternative %s deleted' % alternative)
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.alternative_list', domain_name=domain.name))
|
||||||
@@ -16,7 +16,10 @@ def domain_list():
|
|||||||
def domain_create():
|
def domain_create():
|
||||||
form = forms.DomainForm()
|
form = forms.DomainForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if models.Domain.query.get(form.name.data):
|
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||||
|
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||||
|
conflicting_relay = models.Relay.query.get(form.name.data)
|
||||||
|
if conflicting_domain or conflicting_alternative or conflicting_relay:
|
||||||
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
||||||
else:
|
else:
|
||||||
domain = models.Domain()
|
domain = models.Domain()
|
||||||
|
|||||||
60
admin/mailu/admin/views/relays.py
Normal file
60
admin/mailu/admin/views/relays.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from mailu.admin import app, db, models, forms, access
|
||||||
|
from mailu import app as flask_app
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import wtforms_components
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/relay', methods=['GET'])
|
||||||
|
@access.global_admin
|
||||||
|
def relay_list():
|
||||||
|
relays = models.Relay.query.all()
|
||||||
|
return flask.render_template('relay/list.html', relays=relays)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/relay/create', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
def relay_create():
|
||||||
|
form = forms.RelayForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||||
|
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||||
|
conflicting_relay = models.Relay.query.get(form.name.data)
|
||||||
|
if conflicting_domain or conflicting_alternative or conflicting_relay:
|
||||||
|
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
||||||
|
else:
|
||||||
|
relay = models.Relay()
|
||||||
|
form.populate_obj(relay)
|
||||||
|
db.session.add(relay)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Relayed domain %s created' % relay)
|
||||||
|
return flask.redirect(flask.url_for('.relay_list'))
|
||||||
|
return flask.render_template('relay/create.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/relay/edit/<relay_name>', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
def relay_edit(relay_name):
|
||||||
|
relay = models.Relay.query.get(relay_name) or flask.abort(404)
|
||||||
|
form = forms.RelayForm(obj=relay)
|
||||||
|
wtforms_components.read_only(form.name)
|
||||||
|
form.name.validators = []
|
||||||
|
if form.validate_on_submit():
|
||||||
|
form.populate_obj(relay)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Relayed domain %s saved' % relay)
|
||||||
|
return flask.redirect(flask.url_for('.relay_list'))
|
||||||
|
return flask.render_template('relay/edit.html', form=form,
|
||||||
|
relay=relay)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/relay/delete/<relay_name>', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
@access.confirmation_required("delete {relay_name}")
|
||||||
|
def relay_delete(relay_name):
|
||||||
|
relay = models.Relay.query.get(relay_name) or flask.abort(404)
|
||||||
|
db.session.delete(relay)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Relayed domain %s deleted' % relay)
|
||||||
|
return flask.redirect(flask.url_for('.relay_list'))
|
||||||
|
|
||||||
29
admin/migrations/versions/c162ac88012a_.py
Normal file
29
admin/migrations/versions/c162ac88012a_.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
""" Add relayed domains
|
||||||
|
|
||||||
|
Revision ID: c162ac88012a
|
||||||
|
Revises: c9a0b4e653cf
|
||||||
|
Create Date: 2017-09-10 20:21:10.011969
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'c162ac88012a'
|
||||||
|
down_revision = 'c9a0b4e653cf'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('relay',
|
||||||
|
sa.Column('created_at', sa.Date(), nullable=False),
|
||||||
|
sa.Column('updated_at', sa.Date(), nullable=True),
|
||||||
|
sa.Column('comment', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=80), nullable=False),
|
||||||
|
sa.Column('smtp', sa.String(length=80), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('name')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('relay')
|
||||||
30
admin/migrations/versions/c9a0b4e653cf_.py
Normal file
30
admin/migrations/versions/c9a0b4e653cf_.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
""" Add alternative domains
|
||||||
|
|
||||||
|
Revision ID: c9a0b4e653cf
|
||||||
|
Revises: 73e56bad5ec5
|
||||||
|
Create Date: 2017-09-03 18:23:36.356527
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'c9a0b4e653cf'
|
||||||
|
down_revision = '73e56bad5ec5'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('alternative',
|
||||||
|
sa.Column('created_at', sa.Date(), nullable=False),
|
||||||
|
sa.Column('updated_at', sa.Date(), nullable=True),
|
||||||
|
sa.Column('comment', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=80), nullable=False),
|
||||||
|
sa.Column('domain_name', sa.String(length=80), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['domain_name'], ['domain.name'], ),
|
||||||
|
sa.PrimaryKeyConstraint('name')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('alternative')
|
||||||
@@ -71,7 +71,7 @@ services:
|
|||||||
|
|
||||||
antivirus:
|
antivirus:
|
||||||
# build: clamav
|
# build: clamav
|
||||||
image: mailu/clamav:$VERSION
|
image: mailu/$ANTIVIRUS:$VERSION
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env
|
env_file: .env
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ virtual_alias_maps = ${sql}sqlite-virtual_alias_maps.cf
|
|||||||
virtual_mailbox_domains = ${sql}sqlite-virtual_mailbox_domains.cf
|
virtual_mailbox_domains = ${sql}sqlite-virtual_mailbox_domains.cf
|
||||||
virtual_mailbox_maps = $virtual_alias_maps
|
virtual_mailbox_maps = $virtual_alias_maps
|
||||||
|
|
||||||
# Mails are forwarded to Dovecot for delivery
|
# Mails are transported if required, then forwarded to Dovecot for delivery
|
||||||
|
transport_maps = ${sql}sqlite-transport.cf
|
||||||
virtual_transport = lmtp:inet:imap:2525
|
virtual_transport = lmtp:inet:imap:2525
|
||||||
|
|
||||||
# In order to prevent Postfix from running DNS query, enforce the use of the
|
# In order to prevent Postfix from running DNS query, enforce the use of the
|
||||||
|
|||||||
3
postfix/conf/sqlite-transport.cf
Normal file
3
postfix/conf/sqlite-transport.cf
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dbpath = /data/main.db
|
||||||
|
query =
|
||||||
|
SELECT 'smtp:['||smtp||']' FROM relay WHERE name='%s'
|
||||||
@@ -4,7 +4,9 @@ query =
|
|||||||
FROM
|
FROM
|
||||||
(SELECT destination, email, wildcard, localpart FROM alias
|
(SELECT destination, email, wildcard, localpart FROM alias
|
||||||
UNION
|
UNION
|
||||||
SELECT (CASE WHEN forward_enabled=1 THEN (CASE WHEN forward_keep=1 THEN email||',' ELSE '' END)||forward_destination ELSE email END) AS destination, email, 0 as wildcard, localpart FROM user)
|
SELECT (CASE WHEN forward_enabled=1 THEN (CASE WHEN forward_keep=1 THEN email||',' ELSE '' END)||forward_destination ELSE email END) AS destination, email, 0 as wildcard, localpart FROM user
|
||||||
|
UNION
|
||||||
|
SELECT '@'||domain_name as destination, '@'||name as email, 0 as wildcard, '' as localpart FROM alternative)
|
||||||
WHERE
|
WHERE
|
||||||
(
|
(
|
||||||
wildcard = 0
|
wildcard = 0
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
dbpath = /data/main.db
|
dbpath = /data/main.db
|
||||||
query = SELECT name FROM domain WHERE name='%s'
|
query =
|
||||||
|
SELECT name FROM domain WHERE name='%s'
|
||||||
|
UNION
|
||||||
|
SELECT name FROM alternative WHERE name='%s'
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/re
|
|||||||
&& apk add --no-cache rmilter@testing rsyslog bash
|
&& apk add --no-cache rmilter@testing rsyslog bash
|
||||||
|
|
||||||
COPY rmilter.conf /etc/rmilter.conf
|
COPY rmilter.conf /etc/rmilter.conf
|
||||||
|
COPY rmilter-clamav.conf /etc/rmilter-clamav.conf
|
||||||
COPY rsyslog.conf /etc/rsyslog.conf
|
COPY rsyslog.conf /etc/rsyslog.conf
|
||||||
|
|
||||||
COPY start.sh /start.sh
|
COPY start.sh /start.sh
|
||||||
|
|||||||
18
rmilter/rmilter-clamav.conf
Normal file
18
rmilter/rmilter-clamav.conf
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
clamav {
|
||||||
|
# servers - clamav socket definitions in format:
|
||||||
|
servers = antivirus:3310;
|
||||||
|
# connect_timeout - timeout in miliseconds for connecting to clamav
|
||||||
|
connect_timeout = 1s;
|
||||||
|
# port_timeout - timeout in miliseconds for waiting for clamav port response
|
||||||
|
port_timeout = 4s;
|
||||||
|
# results_timeout - timeout in miliseconds for waiting for clamav response
|
||||||
|
results_timeout = 20s;
|
||||||
|
# error_time - time in seconds during which we are counting errors
|
||||||
|
error_time = 10;
|
||||||
|
# dead_time - time in seconds during which we are thinking that server is down
|
||||||
|
dead_time = 300;
|
||||||
|
# maxerrors - maximum number of errors that can occur during error_time to make us thinking that
|
||||||
|
# Default: 10
|
||||||
|
maxerrors = 10;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -20,24 +20,6 @@ strict_auth = no;
|
|||||||
use_dcc = no;
|
use_dcc = no;
|
||||||
use_redis = yes;
|
use_redis = yes;
|
||||||
|
|
||||||
clamav {
|
|
||||||
# servers - clamav socket definitions in format:
|
|
||||||
servers = antivirus:3310;
|
|
||||||
# connect_timeout - timeout in miliseconds for connecting to clamav
|
|
||||||
connect_timeout = 1s;
|
|
||||||
# port_timeout - timeout in miliseconds for waiting for clamav port response
|
|
||||||
port_timeout = 4s;
|
|
||||||
# results_timeout - timeout in miliseconds for waiting for clamav response
|
|
||||||
results_timeout = 20s;
|
|
||||||
# error_time - time in seconds during which we are counting errors
|
|
||||||
error_time = 10;
|
|
||||||
# dead_time - time in seconds during which we are thinking that server is down
|
|
||||||
dead_time = 300;
|
|
||||||
# maxerrors - maximum number of errors that can occur during error_time to make us thinking that
|
|
||||||
# Default: 10
|
|
||||||
maxerrors = 10;
|
|
||||||
};
|
|
||||||
|
|
||||||
spamd {
|
spamd {
|
||||||
# servers - spamd socket definitions in format:
|
# servers - spamd socket definitions in format:
|
||||||
servers = r:antispam:11333;
|
servers = r:antispam:11333;
|
||||||
|
|||||||
@@ -8,5 +8,9 @@ for VARIABLE in `env | cut -f1 -d=`; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
rm -f /var/run/rsyslogd.pid
|
rm -f /var/run/rsyslogd.pid
|
||||||
|
if [ "$ANTIVIRUS" == "clamav" ];
|
||||||
|
then
|
||||||
|
echo ".try_include /etc/rmilter-clamav.conf" >> /etc/rmilter.conf
|
||||||
|
fi
|
||||||
rmilter -c /etc/rmilter.conf
|
rmilter -c /etc/rmilter.conf
|
||||||
rsyslogd -n
|
rsyslogd -n
|
||||||
|
|||||||
Reference in New Issue
Block a user