Merge pull request #257 from Mailu/feature-relays

Implement relaying emails to specific SMTP
master
kaiyou 7 years ago committed by GitHub
commit 68ba514297

@ -28,5 +28,6 @@ from mailu.admin.views import \
aliases, \ aliases, \
users, \ users, \
domains, \ domains, \
relays, \
alternatives, \ alternatives, \
fetches fetches

@ -56,6 +56,13 @@ class AlternativeForm(flask_wtf.FlaskForm):
submit = fields.SubmitField(_('Create')) 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()])

@ -116,6 +116,20 @@ class Alternative(Base):
return self.name 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).
""" """

@ -0,0 +1,5 @@
{% extends "form.html" %}
{% block title %}
{% trans %}New relay domain{% endtrans %}
{% endblock %}

@ -0,0 +1,9 @@
{% extends "form.html" %}
{% block title %}
{% trans %}Edit relayd domain{% endtrans %}
{% endblock %}
{% block subtitle %}
{{ relay }}
{% endblock %}

@ -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>&nbsp;
<a href="{{ url_for('.relay_delete', relay_name=relay.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>&nbsp;
</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>

@ -19,7 +19,8 @@ def alternative_create(domain_name):
if form.validate_on_submit(): if form.validate_on_submit():
conflicting_domain = 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_alternative = models.Alternative.query.get(form.name.data)
if conflicting_domain or conflicting_alternative: 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:
alternative = models.Alternative(domain=domain) alternative = models.Alternative(domain=domain)

@ -18,7 +18,8 @@ def domain_create():
if form.validate_on_submit(): if form.validate_on_submit():
conflicting_domain = 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_alternative = models.Alternative.query.get(form.name.data)
if conflicting_domain or conflicting_alternative: 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()

@ -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'))

@ -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')

@ -90,7 +90,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

@ -0,0 +1,3 @@
dbpath = /data/main.db
query =
SELECT 'smtp:['||smtp||']' FROM relay WHERE name='%s'
Loading…
Cancel
Save